WordPress/wp-includes/js/media/views/attachments/browser.js

460 lines
13 KiB
JavaScript

/*globals wp, _, jQuery */
/**
* wp.media.view.AttachmentsBrowser
*
* @class
* @augments wp.media.View
* @augments wp.Backbone.View
* @augments Backbone.View
*
* @param {object} options
* @param {object} [options.filters=false] Which filters to show in the browser's toolbar.
* Accepts 'uploaded' and 'all'.
* @param {object} [options.search=true] Whether to show the search interface in the
* browser's toolbar.
* @param {object} [options.date=true] Whether to show the date filter in the
* browser's toolbar.
* @param {object} [options.display=false] Whether to show the attachments display settings
* view in the sidebar.
* @param {bool|string} [options.sidebar=true] Whether to create a sidebar for the browser.
* Accepts true, false, and 'errors'.
*/
var View = require( '../view.js' ),
Library = require( '../attachment/library.js' ),
Toolbar = require( '../toolbar.js' ),
Spinner = require( '../spinner.js' ),
Search = require( '../search.js' ),
Label = require( '../label.js' ),
Uploaded = require( '../attachment-filters/uploaded.js' ),
All = require( '../attachment-filters/all.js' ),
DateFilter = require( '../attachment-filters/date.js' ),
UploaderInline = require( '../uploader/inline.js' ),
Attachments = require( '../attachments.js' ),
Sidebar = require( '../sidebar.js' ),
UploaderStatus = require( '../uploader/status.js' ),
Details = require( '../attachment/details.js' ),
AttachmentCompat = require( '../attachment-compat.js' ),
AttachmentDisplay = require( '../settings/attachment-display.js' ),
mediaTrash = wp.media.view.settings.mediaTrash,
l10n = wp.media.view.l10n,
$ = jQuery,
AttachmentsBrowser;
AttachmentsBrowser = View.extend({
tagName: 'div',
className: 'attachments-browser',
initialize: function() {
_.defaults( this.options, {
filters: false,
search: true,
date: true,
display: false,
sidebar: true,
AttachmentView: Library
});
this.listenTo( this.controller, 'toggle:upload:attachment', _.bind( this.toggleUploader, this ) );
this.controller.on( 'edit:selection', this.editSelection );
this.createToolbar();
if ( this.options.sidebar ) {
this.createSidebar();
}
this.createUploader();
this.createAttachments();
this.updateContent();
if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
this.$el.addClass( 'hide-sidebar' );
if ( 'errors' === this.options.sidebar ) {
this.$el.addClass( 'sidebar-for-errors' );
}
}
this.collection.on( 'add remove reset', this.updateContent, this );
},
editSelection: function( modal ) {
modal.$( '.media-button-backToLibrary' ).focus();
},
/**
* @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
*/
dispose: function() {
this.options.selection.off( null, null, this );
View.prototype.dispose.apply( this, arguments );
return this;
},
createToolbar: function() {
var LibraryViewSwitcher, Filters, toolbarOptions;
toolbarOptions = {
controller: this.controller
};
if ( this.controller.isModeActive( 'grid' ) ) {
toolbarOptions.className = 'media-toolbar wp-filter';
}
/**
* @member {wp.media.view.Toolbar}
*/
this.toolbar = new Toolbar( toolbarOptions );
this.views.add( this.toolbar );
this.toolbar.set( 'spinner', new Spinner({
priority: -60
}) );
if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
// "Filters" will return a <select>, need to render
// screen reader text before
this.toolbar.set( 'filtersLabel', new Label({
value: l10n.filterByType,
attributes: {
'for': 'media-attachment-filters'
},
priority: -80
}).render() );
if ( 'uploaded' === this.options.filters ) {
this.toolbar.set( 'filters', new Uploaded({
controller: this.controller,
model: this.collection.props,
priority: -80
}).render() );
} else {
Filters = new All({
controller: this.controller,
model: this.collection.props,
priority: -80
});
this.toolbar.set( 'filters', Filters.render() );
}
}
// Feels odd to bring the global media library switcher into the Attachment
// browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
// which the controller can tap into and add this view?
if ( this.controller.isModeActive( 'grid' ) ) {
LibraryViewSwitcher = View.extend({
className: 'view-switch media-grid-view-switch',
template: wp.template( 'media-library-view-switcher')
});
this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
controller: this.controller,
priority: -90
}).render() );
// DateFilter is a <select>, screen reader text needs to be rendered before
this.toolbar.set( 'dateFilterLabel', new Label({
value: l10n.filterByDate,
attributes: {
'for': 'media-attachment-date-filters'
},
priority: -75
}).render() );
this.toolbar.set( 'dateFilter', new DateFilter({
controller: this.controller,
model: this.collection.props,
priority: -75
}).render() );
// BulkSelection is a <div> with subviews, including screen reader text
this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
text: l10n.bulkSelect,
controller: this.controller,
priority: -70
}).render() );
this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
filters: Filters,
style: 'primary',
disabled: true,
text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
controller: this.controller,
priority: -60,
click: function() {
var changed = [], removed = [],
selection = this.controller.state().get( 'selection' ),
library = this.controller.state().get( 'library' );
if ( ! selection.length ) {
return;
}
if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
return;
}
if ( mediaTrash &&
'trash' !== selection.at( 0 ).get( 'status' ) &&
! window.confirm( l10n.warnBulkTrash ) ) {
return;
}
selection.each( function( model ) {
if ( ! model.get( 'nonces' )['delete'] ) {
removed.push( model );
return;
}
if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
model.set( 'status', 'inherit' );
changed.push( model.save() );
removed.push( model );
} else if ( mediaTrash ) {
model.set( 'status', 'trash' );
changed.push( model.save() );
removed.push( model );
} else {
model.destroy({wait: true});
}
} );
if ( changed.length ) {
selection.remove( removed );
$.when.apply( null, changed ).then( _.bind( function() {
library._requery( true );
this.controller.trigger( 'selection:action:done' );
}, this ) );
} else {
this.controller.trigger( 'selection:action:done' );
}
}
}).render() );
if ( mediaTrash ) {
this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
filters: Filters,
style: 'primary',
disabled: true,
text: l10n.deleteSelected,
controller: this.controller,
priority: -55,
click: function() {
var removed = [], selection = this.controller.state().get( 'selection' );
if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
return;
}
selection.each( function( model ) {
if ( ! model.get( 'nonces' )['delete'] ) {
removed.push( model );
return;
}
model.destroy();
} );
selection.remove( removed );
this.controller.trigger( 'selection:action:done' );
}
}).render() );
}
} else if ( this.options.date ) {
// DateFilter is a <select>, screen reader text needs to be rendered before
this.toolbar.set( 'dateFilterLabel', new Label({
value: l10n.filterByDate,
attributes: {
'for': 'media-attachment-date-filters'
},
priority: -75
}).render() );
this.toolbar.set( 'dateFilter', new DateFilter({
controller: this.controller,
model: this.collection.props,
priority: -75
}).render() );
}
if ( this.options.search ) {
// Search is an input, screen reader text needs to be rendered before
this.toolbar.set( 'searchLabel', new Label({
value: l10n.searchMediaLabel,
attributes: {
'for': 'media-search-input'
},
priority: 60
}).render() );
this.toolbar.set( 'search', new Search({
controller: this.controller,
model: this.collection.props,
priority: 60
}).render() );
}
if ( this.options.dragInfo ) {
this.toolbar.set( 'dragInfo', new View({
el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
priority: -40
}) );
}
if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
this.toolbar.set( 'suggestedDimensions', new View({
el: $( '<div class="instructions">' + l10n.suggestedDimensions + ' ' + this.options.suggestedWidth + ' &times; ' + this.options.suggestedHeight + '</div>' )[0],
priority: -40
}) );
}
},
updateContent: function() {
var view = this,
noItemsView;
if ( this.controller.isModeActive( 'grid' ) ) {
noItemsView = view.attachmentsNoResults;
} else {
noItemsView = view.uploader;
}
if ( ! this.collection.length ) {
this.toolbar.get( 'spinner' ).show();
this.dfd = this.collection.more().done( function() {
if ( ! view.collection.length ) {
noItemsView.$el.removeClass( 'hidden' );
} else {
noItemsView.$el.addClass( 'hidden' );
}
view.toolbar.get( 'spinner' ).hide();
} );
} else {
noItemsView.$el.addClass( 'hidden' );
view.toolbar.get( 'spinner' ).hide();
}
},
createUploader: function() {
this.uploader = new UploaderInline({
controller: this.controller,
status: false,
message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
canClose: this.controller.isModeActive( 'grid' )
});
this.uploader.hide();
this.views.add( this.uploader );
},
toggleUploader: function() {
if ( this.uploader.$el.hasClass( 'hidden' ) ) {
this.uploader.show();
} else {
this.uploader.hide();
}
},
createAttachments: function() {
this.attachments = new Attachments({
controller: this.controller,
collection: this.collection,
selection: this.options.selection,
model: this.model,
sortable: this.options.sortable,
scrollElement: this.options.scrollElement,
idealColumnWidth: this.options.idealColumnWidth,
// The single `Attachment` view to be used in the `Attachments` view.
AttachmentView: this.options.AttachmentView
});
// Add keydown listener to the instance of the Attachments view
this.attachments.listenTo( this.controller, 'attachment:keydown:arrow', this.attachments.arrowEvent );
this.attachments.listenTo( this.controller, 'attachment:details:shift-tab', this.attachments.restoreFocus );
this.views.add( this.attachments );
if ( this.controller.isModeActive( 'grid' ) ) {
this.attachmentsNoResults = new View({
controller: this.controller,
tagName: 'p'
});
this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
this.attachmentsNoResults.$el.html( l10n.noMedia );
this.views.add( this.attachmentsNoResults );
}
},
createSidebar: function() {
var options = this.options,
selection = options.selection,
sidebar = this.sidebar = new Sidebar({
controller: this.controller
});
this.views.add( sidebar );
if ( this.controller.uploader ) {
sidebar.set( 'uploads', new UploaderStatus({
controller: this.controller,
priority: 40
}) );
}
selection.on( 'selection:single', this.createSingle, this );
selection.on( 'selection:unsingle', this.disposeSingle, this );
if ( selection.single() ) {
this.createSingle();
}
},
createSingle: function() {
var sidebar = this.sidebar,
single = this.options.selection.single();
sidebar.set( 'details', new Details({
controller: this.controller,
model: single,
priority: 80
}) );
sidebar.set( 'compat', new AttachmentCompat({
controller: this.controller,
model: single,
priority: 120
}) );
if ( this.options.display ) {
sidebar.set( 'display', new AttachmentDisplay({
controller: this.controller,
model: this.model.display( single ),
attachment: single,
priority: 160,
userSettings: this.model.get('displayUserSettings')
}) );
}
// Show the sidebar on mobile
if ( this.model.id === 'insert' ) {
sidebar.$el.addClass( 'visible' );
}
},
disposeSingle: function() {
var sidebar = this.sidebar;
sidebar.unset('details');
sidebar.unset('compat');
sidebar.unset('display');
// Hide the sidebar on mobile
sidebar.$el.removeClass( 'visible' );
}
});
module.exports = AttachmentsBrowser;