Commit cf1e3ee7 authored by plach's avatar plach

Issue #2962525 by samuel.mortenson, jrockowitz, seanB, drpal, chr.fritsch,...

Issue #2962525 by samuel.mortenson, jrockowitz, seanB, drpal, chr.fritsch, ckrina, phenaproxima, webchick, lauriii, beautifulmind, andrewmacpherson, xjm, Gábor Hojtsy: Create a field widget for the Media library module

(cherry picked from commit de52834d)
parent 8d8925e7
......@@ -243,31 +243,9 @@ function media_field_widget_multivalue_form_alter(array &$elements, FormStateInt
// Retrieve the media bundle list and add information for the user based on
// which bundles are available to be created or referenced.
$settings = $context['items']->getFieldDefinition()->getSetting('handler_settings');
$allowed_bundles = isset($settings['target_bundles']) ? $settings['target_bundles'] : [];
$access_handler = \Drupal::entityTypeManager()->getAccessControlHandler('media');
$all_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('media');
$bundle_labels = [];
$create_bundles = [];
foreach ($allowed_bundles as $bundle) {
$bundle_labels[] = $all_bundles[$bundle]['label'];
if ($access_handler->createAccess($bundle)) {
$create_bundles[] = $bundle;
if (count($create_bundles) > 1) {
// If the user has access to create more than 1 bundle then the
// individual media type form can not be used.
break;
}
}
}
// Add a section about how to create media if the user has access to do so.
if (!empty($create_bundles)) {
if (count($create_bundles) === 1) {
$add_url = Url::fromRoute('entity.media.add_form', ['media_type' => $create_bundles[0]])->toString();
}
elseif (count($create_bundles) > 1) {
$add_url = Url::fromRoute('entity.media.add_page')->toString();
}
$allowed_bundles = !empty($settings['target_bundles']) ? $settings['target_bundles'] : [];
$add_url = _media_get_add_url($allowed_bundles);
if ($add_url) {
$elements['#media_help']['#media_add_help'] = t('Create your media on the <a href=":add_page" target="_blank">media add page</a> (opens a new window), then add it by name to the field below.', [':add_page' => $add_url]);
}
......@@ -308,6 +286,10 @@ function media_field_widget_multivalue_form_alter(array &$elements, FormStateInt
if ($overview_url->access()) {
$elements['#media_help']['#media_list_link'] = t('See the <a href=":list_url" target="_blank">media list</a> (opens a new window) to help locate media.', [':list_url' => $overview_url->toString()]);
}
$all_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('media');
$bundle_labels = array_map(function ($bundle) use ($all_bundles) {
return $all_bundles[$bundle]['label'];
}, $allowed_bundles);
$elements['#media_help']['#allowed_types_help'] = t('Allowed media types: %types', ['%types' => implode(", ", $bundle_labels)]);
}
}
......@@ -336,3 +318,32 @@ function media_preprocess_media_reference_help(&$variables) {
}
}
}
/**
* Returns the appropriate URL to add media for the current user.
*
* @todo Remove in https://www.drupal.org/project/drupal/issues/2938116
*
* @param string[] $allowed_bundles
* An array of bundles that should be checked for create access.
*
* @return bool|\Drupal\Core\Url
* The URL to add media, or FALSE if the user cannot create any media.
*
* @internal
* This function is internal and may be removed in a minor release.
*/
function _media_get_add_url($allowed_bundles) {
$access_handler = \Drupal::entityTypeManager()->getAccessControlHandler('media');
$create_bundles = array_filter($allowed_bundles, [$access_handler, 'createAccess']);
// Add a section about how to create media if the user has access to do so.
if (count($create_bundles) === 1) {
return Url::fromRoute('entity.media.add_form', ['media_type' => reset($create_bundles)])->toString();
}
elseif (count($create_bundles) > 1) {
return Url::fromRoute('entity.media.add_page')->toString();
}
return FALSE;
}
......@@ -8,6 +8,7 @@ dependencies:
- media_library
module:
- media
- media_library
- user
id: media_library
label: 'Media library'
......@@ -71,7 +72,7 @@ display:
type: default
options:
grouping: { }
row_class: 'media-library-item js-click-to-select'
row_class: 'media-library-item js-media-library-item js-click-to-select'
default_row_class: true
row:
type: fields
......@@ -118,7 +119,7 @@ display:
preserve_tags: ''
html: false
element_type: ''
element_class: js-click-to-select__checkbox
element_class: js-click-to-select-checkbox
element_label_type: ''
element_label_class: ''
element_label_colon: false
......@@ -376,51 +377,9 @@ display:
content: 'No media available.'
plugin_id: text_custom
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: false
validate:
type: none
fail: 'not found'
validate_options: { }
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
css_class: media-library-view
css_class: 'media-library-view js-media-library-view'
cache_metadata:
max-age: 0
contexts:
......@@ -456,3 +415,131 @@ display:
- 'url.query_args:sort_by'
- user.permissions
tags: { }
# @todo Lock down access in https://www.drupal.org/node/2983179
widget:
display_plugin: page
id: widget
display_title: Widget
position: 2
display_options:
display_extenders: { }
path: admin/content/media-widget
fields:
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
media_library_select_form:
id: media_library_select_form
table: media
field: media_library_select_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: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: js-click-to-select-checkbox
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
entity_type: media
plugin_id: media_library_select_form
defaults:
fields: false
access: false
display_description: ''
access:
type: perm
options:
perm: 'view media'
cache_metadata:
max-age: -1
contexts:
- 'languages:language_interface'
- url
- url.query_args
- 'url.query_args:sort_by'
- user.permissions
tags: { }
......@@ -2,22 +2,19 @@
* @file media_library.module.css
*/
.media-library-page-form {
display: flex;
flex-wrap: wrap;
}
.media-library-page-form > .form-actions {
.media-library-views-form > .form-actions {
flex-basis: 100%;
}
.media-library-page-form__header > div,
.media-library-views-form,
.media-library-selection,
.media-library-views-form__bulk_form,
.media-library-view .form--inline {
display: flex;
flex-wrap: wrap;
}
.media-library-page-form__header {
.media-library-views-form__header {
flex-basis: 100%;
}
......@@ -25,7 +22,7 @@
position: relative;
}
.media-library-item .js-click-to-select__trigger {
.media-library-item .js-click-to-select-trigger {
overflow: hidden;
cursor: pointer;
}
......@@ -34,7 +31,7 @@
align-self: flex-end;
}
.media-library-item .js-click-to-select__checkbox {
.media-library-item .js-click-to-select-checkbox {
position: absolute;
display: block;
z-index: 1;
......@@ -51,6 +48,25 @@
.media-library-select-all {
flex-basis: 100%;
width: 100%;
}
.media-library-view.view-display-id-widget .media-library-select-all {
display: none;
}
.media-library-item--disabled {
pointer-events: none;
}
.media-library-selection .media-library-item__preview {
cursor: move;
}
/* @todo Remove or re-work in https://www.drupal.org/node/2985168 */
.media-library-widget .media-library-item__name a,
.media-library-view.view-display-id-widget .media-library-item__name a {
pointer-events: none;
}
@media screen and (max-width: 600px) {
......
......@@ -5,7 +5,7 @@
* @see https://www.drupal.org/project/drupal/issues/2980769
*/
.media-library-page-form__header .form-item {
.media-library-views-form__header .form-item {
margin-right: 8px;
}
......@@ -16,21 +16,29 @@
.media-library-item {
justify-content: center;
vertical-align: top;
padding: 2px;
border: 1px solid #ebebeb;
border: 1px solid #dbdbdb;
margin: 16px 16px 2px 2px;
width: 180px;
background: #fff;
transition: border-color 0.2s, color 0.2s, background 0.2s;
}
.media-library-view {
min-height: 300px;
}
.media-library-view .form-actions {
margin: 0.75em 0;
}
.media-library-view .media-library-view--form-actions {
clear: left;
margin: 0.75em 0;
align-self: flex-end;
}
.media-library-item .field--name-thumbnail {
background-color: #ebebeb;
margin: 2px;
overflow: hidden;
text-align: center;
}
......@@ -54,17 +62,17 @@
border-color: #0076c0;
}
.media-library-item .js-click-to-select__checkbox input {
.media-library-item .js-click-to-select-checkbox input {
width: 30px;
height: 30px;
}
.media-library-item .js-click-to-select__checkbox .form-item {
.media-library-item .js-click-to-select-checkbox .form-item {
margin: 0;
}
.media-library-item__preview {
padding-bottom: 44px;
padding-bottom: 34px;
}
.media-library-item__status {
......@@ -90,9 +98,9 @@
position: absolute;
bottom: 0;
display: block;
padding: 10px;
max-width: calc(100% - 20px);
max-height: calc(100% - 60px);
padding: 5px;
max-width: calc(100% - 10px);
max-height: calc(100% - 50px);
overflow: hidden;
background: white;
}
......@@ -135,6 +143,68 @@
margin-right: 10px;
}
.media-library-item--disabled {
opacity: 0.5;
}
.media-library-selection {
margin-bottom: 1.5rem;
}
.media-library-widget {
position: relative;
}
.media-library-widget__toggle-weight {
position: absolute;
right: 5px;
top: 5px;
}
.media-library-item .form-item {
margin: 0.75em;
}
.media-library-item__remove,
.media-library-item__remove:hover,
.media-library-item__remove:focus,
.media-library-item__remove.button,
.media-library-item__remove.button:disabled,
.media-library-item__remove.button:disabled:active,
.media-library-item__remove.button:hover,
.media-library-item__remove.button:focus {
position: absolute;
z-index: 1;
top: 0;
right: 0;
width: 24px;
height: 24px;
margin: 5px;
padding: 0;
background: url('../../../misc/icons/787878/ex.svg') #fff center no-repeat;
background-size: 16px 16px;
border: 2px solid #ccc;
border-radius: 20px;
color: transparent;
text-shadow: none;
transition: 0.2s border-color;
}
.media-library-item__remove:hover,
.media-library-item__remove:focus,
.media-library-item__remove.button:hover,
.media-library-item__remove.button:focus,
.media-library-item__remove.button:disabled:active {
border-color: #40b6ff;
}
/* @todo Remove or re-work in https://www.drupal.org/node/2985168 */
.media-library-widget .media-library-item__name a,
.media-library-view.view-display-id-widget .media-library-item__name a {
color: black;
text-decoration: none;
}
@media screen and (max-width: 600px) {
.media-library-item {
width: 150px;
......
......@@ -8,7 +8,7 @@
*/
Drupal.behaviors.ClickToSelect = {
attach(context) {
$('.js-click-to-select__trigger', context)
$('.js-click-to-select-trigger', context)
.once('media-library-click-to-select')
.on('click', (event) => {
// Links inside the trigger should not be click-able.
......@@ -16,10 +16,10 @@
// Click the hidden checkbox when the trigger is clicked.
const $input = $(event.currentTarget)
.closest('.js-click-to-select')
.find('.js-click-to-select__checkbox input');
.find('.js-click-to-select-checkbox input');
$input.prop('checked', !$input.prop('checked')).trigger('change');
});
$('.js-click-to-select__checkbox input', context)
$('.js-click-to-select-checkbox input', context)
.once('media-library-click-to-select')
.on('change', ({ currentTarget }) => {
$(currentTarget)
......
......@@ -8,13 +8,13 @@
(function ($, Drupal) {
Drupal.behaviors.ClickToSelect = {
attach: function attach(context) {
$('.js-click-to-select__trigger', context).once('media-library-click-to-select').on('click', function (event) {
$('.js-click-to-select-trigger', context).once('media-library-click-to-select').on('click', function (event) {
event.preventDefault();
var $input = $(event.currentTarget).closest('.js-click-to-select').find('.js-click-to-select__checkbox input');
var $input = $(event.currentTarget).closest('.js-click-to-select').find('.js-click-to-select-checkbox input');
$input.prop('checked', !$input.prop('checked')).trigger('change');
});
$('.js-click-to-select__checkbox input', context).once('media-library-click-to-select').on('change', function (_ref) {
$('.js-click-to-select-checkbox input', context).once('media-library-click-to-select').on('change', function (_ref) {
var currentTarget = _ref.currentTarget;
$(currentTarget).closest('.js-click-to-select').toggleClass('checked', $(currentTarget).prop('checked'));
......
......@@ -7,7 +7,7 @@
*/
Drupal.behaviors.MediaLibraryHover = {
attach(context) {
$('.media-library-item .js-click-to-select__trigger,.media-library-item .js-click-to-select__checkbox', context).once('media-library-item-hover')
$('.media-library-item .js-click-to-select-trigger,.media-library-item .js-click-to-select-checkbox', context).once('media-library-item-hover')
.on('mouseover mouseout', ({ currentTarget, type }) => {
$(currentTarget).closest('.media-library-item').toggleClass('is-hover', type === 'mouseover');
});
......@@ -19,7 +19,7 @@
*/
Drupal.behaviors.MediaLibraryFocus = {
attach(context) {
$('.media-library-item .js-click-to-select__checkbox input', context).once('media-library-item-focus')
$('.media-library-item .js-click-to-select-checkbox input', context).once('media-library-item-focus')
.on('focus blur', ({ currentTarget, type }) => {
$(currentTarget).closest('.media-library-item').toggleClass('is-focus', type === 'focus');
});
......
......@@ -8,7 +8,7 @@
(function ($, Drupal) {
Drupal.behaviors.MediaLibraryHover = {
attach: function attach(context) {
$('.media-library-item .js-click-to-select__trigger,.media-library-item .js-click-to-select__checkbox', context).once('media-library-item-hover').on('mouseover mouseout', function (_ref) {
$('.media-library-item .js-click-to-select-trigger,.media-library-item .js-click-to-select-checkbox', context).once('media-library-item-hover').on('mouseover mouseout', function (_ref) {
var currentTarget = _ref.currentTarget,
type = _ref.type;
......@@ -19,7 +19,7 @@
Drupal.behaviors.MediaLibraryFocus = {
attach: function attach(context) {
$('.media-library-item .js-click-to-select__checkbox input', context).once('media-library-item-focus').on('focus blur', function (_ref2) {
$('.media-library-item .js-click-to-select-checkbox input', context).once('media-library-item-focus').on('focus blur', function (_ref2) {
var currentTarget = _ref2.currentTarget,
type = _ref2.type;
......
/**
* @file media_library.widget.js
*/
(($, Drupal) => {
/**
* Allows users to re-order their selection with drag+drop.
*/
Drupal.behaviors.MediaLibraryWidgetSortable = {
attach(context) {
// Allow media items to be re-sorted with drag+drop in the widget.
$('.js-media-library-selection', context).once('media-library-sortable').sortable({
tolerance: 'pointer',
helper: 'clone',
handle: '.js-media-library-item-preview',
stop: ({ target }) => {
// Update all the hidden "weight" fields.
$(target).children().each((index, child) => {
$(child).find('.js-media-library-item-weight').val(index);
});
},
});
},
};
/**
* Allows selection order to be set without drag+drop for accessibility.
*/
Drupal.behaviors.MediaLibraryWidgetToggleWeight = {
attach(context) {
const strings = {
show: Drupal.t('Show media item weights'),
hide: Drupal.t('Hide media item weights'),
};
$('.js-media-library-widget-toggle-weight', context).once('media-library-toggle')
.on('click', (e) => {
e.preventDefault();
$(e.currentTarget)
.toggleClass('active')
.text($(e.currentTarget).hasClass('active') ? strings.hide : strings.show)
.parent()
.find('.js-media-library-item-weight')
.parent()
.toggle();
})
.text(strings.show);
$('.js-media-library-item-weight', context).once('media-library-toggle').parent().hide();
},
};
/**
* Warn users when clicking outgoing links from the library or widget.
*/
Drupal.behaviors.MediaLibraryWidgetWarn = {
attach(context) {
$('.js-media-library-item a[href]', context).once('media-library-warn-link')
.on('click', (e) => {
const message = Drupal.t('Unsaved changes to the form will be lost. Are you sure you want to leave?');
const confirmation = window.confirm(message);
if (!confirmation) {
e.preventDefault();
}
});
},
};
/**
* Prevent users from selecting more items than allowed in the view.
*/
Drupal.behaviors.MediaLibraryWidgetRemaining = {
attach(context, settings) {
const $view = $('.js-media-library-view', context).once('media-library-remaining');
$view.find('.js-media-library-item input[type="checkbox"]')
.on('change', () => {
if (settings.media_library && settings.media_library.selection_remaining) {
const $checkboxes = $view.find('.js-media-library-item input[type="checkbox"]');
if ($checkboxes.filter(':checked').length === settings.media_library.selection_remaining) {
$checkboxes
.not(':checked')
.prop('disabled', true)
.closest('.js-media-library-item')
.addClass('media-library-item--disabled');
}
else {
$checkboxes
.prop('disabled', false)
.closest('.js-media-library-item')
.removeClass('media-library-item--disabled');
}
}
});
},
};
})(jQuery, Drupal);
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal) {
Drupal.behaviors.MediaLibraryWidgetSortable = {
attach: function attach(context) {
$('.js-media-library-selection', context).once('media-library-sortable').sortable({
tolerance: 'pointer',
helper: 'clone',
handle: '.js-media-library-item-preview',
stop: function stop(_ref) {
var target = _ref.target;
$(target).children().each(function (index, child) {
$(child).find('.js-media-library-item-weight').val(index);
});
}
});
}
};
Drupal.behaviors