diff --git a/src/wp-includes/css/media-views.css b/src/wp-includes/css/media-views.css index 8a5990e1bc..87ccdc6589 100644 --- a/src/wp-includes/css/media-views.css +++ b/src/wp-includes/css/media-views.css @@ -228,7 +228,6 @@ .media-toolbar-secondary > .media-button, .media-toolbar-secondary > .media-button-group { margin-right: 10px; - float: left; margin-top: 15px; } @@ -996,6 +995,25 @@ bottom: 0; overflow: auto; outline: none; + -webkit-transition: 1s ease-in-out; + transition: 1s ease-in-out; +} + +.attachments-browser .uploader-inline:not(.hidden) + .attachments { + -webkit-transform: translateY( 300px ); + -ms-transform: translateY( 300px ); + transform: translateY( 300px ); +} + +.attachments-browser .uploader-inline.hidden { + display: block; + -webkit-transform: translateY( -100% ); + -ms-transform: translateY( -100% ); + transform: translateY( -100% ); +} + +.attachments-browser .uploader-inline-content { + top: 0; } .inline-toolbar { @@ -1081,7 +1099,8 @@ video#inline-media-node { left: 0; } -.attachments-browser.hide-sidebar .attachments { +.attachments-browser.hide-sidebar .attachments, +.attachments-browser.hide-sidebar .uploader-inline { right: 0; margin-right: 0; } @@ -2657,6 +2676,11 @@ video#inline-media-node { left: 0; } +.edit-attachment-frame .media-frame-content { + border-bottom: none; + bottom: 0; +} + /* Hiding this for the moment instead of removing it from the template. */ .edit-attachment-frame h3 { display: none; diff --git a/src/wp-includes/js/media-grid.js b/src/wp-includes/js/media-grid.js index 0ebbc67af9..9e69a4d1ba 100644 --- a/src/wp-includes/js/media-grid.js +++ b/src/wp-includes/js/media-grid.js @@ -25,7 +25,6 @@ menu: false, router: 'edit-metadata', content: 'edit-metadata', - toolbar: 'toolbar', url: '' }, @@ -34,10 +33,6 @@ media.controller._State.prototype.initialize.apply( this, arguments ); }, - activate: function() { - this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar ); - }, - _postActivate: function() { this._content(); this._router(); @@ -47,30 +42,6 @@ this.stopListening( this.frame ); }, - toolbar: function() { - var frame = this.frame, - lastState = frame.lastState(), - previous = lastState && lastState.id; - - frame.toolbar.set( new media.view.Toolbar({ - controller: frame, - items: { - back: { - style: 'primary', - text: l10n.back, - priority: 20, - click: function() { - if ( previous ) { - frame.setState( previous ); - } else { - frame.close(); - } - } - } - } - }) ); - }, - /** * @access private */ @@ -125,12 +96,13 @@ modal: false, selection: [], library: {}, - multiple: false, + multiple: 'add', state: 'library', uploader: true, mode: [ 'grid', 'edit' ] }); + $(document).on( 'click', '.add-new-h2', _.bind( this.addNewClickHandler, this ) ); // Ensure core and media grid view UI is enabled. this.$el.addClass('wp-core-ui media-grid-view'); @@ -186,24 +158,33 @@ }, createStates: function() { - var options = this.options; + var options = this.options, + libraryState; if ( this.options.states ) { return; } + libraryState = new media.controller.Library({ + library: media.query( options.library ), + multiple: options.multiple, + title: options.title, + priority: 20, + toolbar: false, + router: false, + content: 'browse', + filterable: 'mime-types' + }); + + libraryState._renderTitle = function( view ) { + var text = this.get('title') || ''; + view.$el.addClass( 'wrap' ); + text += 'Add New'; + view.$el.html( text ); + }; // Add the default states. this.states.add([ - new media.controller.Library({ - library: media.query( options.library ), - multiple: options.multiple, - title: options.title, - priority: 20, - toolbar: false, - router: false, - content: 'browse', - filterable: 'mime-types' - }) + libraryState ]); }, @@ -217,6 +198,10 @@ this.on( 'edit:attachment:previous', this.editPreviousAttachment, this ); }, + addNewClickHandler: function() { + this.trigger( 'show:upload:attachment' ); + }, + editPreviousAttachment: function( currentModel ) { var library = this.state().get('library'), currentModelIndex = library.indexOf( currentModel ); @@ -233,26 +218,17 @@ * Open the Edit Attachment modal. */ editAttachment: function( model ) { - var library = this.state().get('library'), hasPrevious, hasNext; - if ( library.indexOf( model ) > 0 ) { - hasPrevious = true; - } - else { - hasPrevious = false; - } - if ( library.indexOf( model ) < library.length - 1 ) { - hasNext = true; - } - else { - hasNext = false; - } + var library = this.state().get('library'); - new media.view.Frame.EditAttachment({ - hasPrevious: hasPrevious, - hasNext: hasNext, - model: model, - gridController: this + // Create a new EditAttachment frame, passing along the library and the attachment model. + this.editAttachmentFrame = new media.view.Frame.EditAttachment({ + library: library, + model: model }); + + // Listen to events on the edit attachment frame for triggering pagination callback handlers. + this.listenTo( this.editAttachmentFrame, 'edit:attachment:next', this.editNextAttachment ); + this.listenTo( this.editAttachmentFrame, 'edit:attachment:previous', this.editPreviousAttachment ); }, /** @@ -363,6 +339,9 @@ this.on( 'router:render', this.browseRouter, this ); } + this.options.hasPrevious = ( this.options.library.indexOf( this.options.model ) > 0 ) ? true : false; + this.options.hasNext = ( this.options.library.indexOf( this.options.model ) < this.options.library.length - 1 ) ? true : false; + // Initialize modal container view. if ( this.options.modal ) { this.modal = new media.view.Modal({ @@ -471,7 +450,7 @@ if ( ! this.options.hasPrevious ) return; this.modal.close(); - this.options.gridController.trigger( 'edit:attachment:previous', this.model ); + this.trigger( 'edit:attachment:previous', this.model ); }, /** @@ -481,9 +460,8 @@ if ( ! this.options.hasNext ) return; this.modal.close(); - this.options.gridController.trigger( 'edit:attachment:next', this.model ); + this.trigger( 'edit:attachment:next', this.model ); } - }); media.view.GridFieldOptions = media.View.extend({ @@ -514,4 +492,58 @@ } }); + media.view.BulkSelectionToggleButton = media.view.Button.extend({ + initialize: function() { + media.view.Button.prototype.initialize.apply( this, arguments ); + this.listenTo( this.controller, 'bulk-edit:activate bulk-edit:deactivate', _.bind( this.toggleBulkEditHandler, this ) ); + }, + + click: function() { + var bulkEditActive = this.controller.activeModes.where( { id: 'bulk-edit' } ).length; + media.view.Button.prototype.click.apply( this, arguments ); + + if ( bulkEditActive ) { + this.controller.deactivateMode( 'bulk-edit' ); + this.controller.activateMode( 'edit' ); + } else { + this.controller.deactivateMode( 'edit' ); + this.controller.activateMode( 'bulk-edit' ); + } + }, + + toggleBulkEditHandler: function() { + var bulkEditActive = this.controller.activeModes.where( { id: 'bulk-edit' } ).length; + if ( bulkEditActive ) { + this.$el.addClass( 'button-primary' ); + } else { + this.$el.removeClass( 'button-primary' ); + this.controller.state().get('selection').reset(); + } + } + }); + + media.view.BulkDeleteButton = media.view.Button.extend({ + initialize: function() { + media.view.Button.prototype.initialize.apply( this, arguments ); + this.$el.hide(); + this.listenTo( this.controller, 'bulk-edit:activate bulk-edit:deactivate', _.bind( this.visibility, this ) ); + }, + + click: function() { + media.view.Button.prototype.click.apply( this, arguments ); + while (this.controller.state().get('selection').length > 0) { + this.controller.state().get('selection').at(0).destroy(); + } + }, + + visibility: function() { + var bulkEditActive = this.controller.activeModes.where( { id: 'bulk-edit' } ).length; + if ( bulkEditActive ) { + this.$el.show(); + } else { + this.$el.hide(); + } + } + }); + }(jQuery, _, Backbone, wp)); \ No newline at end of file diff --git a/src/wp-includes/js/media-views.js b/src/wp-includes/js/media-views.js index 6bf147c8a5..328f59a13d 100644 --- a/src/wp-includes/js/media-views.js +++ b/src/wp-includes/js/media-views.js @@ -665,7 +665,7 @@ } if ( ! this.get('edge') ) { - this.set( 'edge', 120 ); + this.set( 'edge', 150 ); } if ( ! this.get('gutter') ) { @@ -1785,14 +1785,13 @@ * @global wp.Uploader */ initialize: function() { - media.view.Frame.prototype.initialize.apply( this, arguments ); _.defaults( this.options, { title: '', modal: true, uploader: true, - mode: ['select'] + mode: [ 'select' ] }); // Ensure core UI is enabled. @@ -1808,6 +1807,14 @@ this.modal.content( this ); } + // Store active "modes" that the frame is in. Unrelated to region modes. + this.activeModes = new Backbone.Collection(); + this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); + + _.each( this.options.mode, function( mode ) { + this.activeModes.add( new Backbone.Model( { id: mode } ) ); + }, this ); + // Force the uploader off if the upload limit has been exceeded or // if the browser isn't supported. if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { @@ -1972,6 +1979,42 @@ window.tb_remove = this._tb_remove; delete this._tb_remove; + }, + + /** + * Map activeMode collection events to the frame. + */ + triggerModeEvents: function( model, collection, options ) { + var collectionEvent, + modeEventMap = { + add: 'activate', + remove: 'deactivate' + }, + eventToTrigger; + // Probably a better way to do this. + _.each( options, function( value, key ) { + if ( value ) { + collectionEvent = key; + } + } ); + + if ( ! _.has( modeEventMap, collectionEvent ) ) + return; + + eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; + this.trigger( eventToTrigger ); + }, + activateMode: function( mode ) { + this.activeModes.add( [ { id: mode } ] ); + this.trigger( mode + ':activate' ); + }, + deactivateMode: function( mode ) { + // Bail if the mode isn't active. + if ( ! this.activeModes.where( { id: mode } ).length ) { + return; + } + this.activeModes.remove( this.activeModes.where( { id: mode } ) ); + this.trigger( mode + ':deactivate' ); } }); @@ -4673,7 +4716,7 @@ } // In the grid view, bubble up an edit:attachment event to the controller. - if ( _.contains( this.controller.options.mode, 'grid' ) ) { + if ( this.controller.activeModes.where( { id: 'edit' } ).length ) { this.controller.trigger( 'edit:attachment', this.model ); return; } @@ -5081,7 +5124,10 @@ if ( this.options.resize ) { $(window).on( 'resize.attachments', this._resizeCss ); } - this.css(); + + // Call this.css() after this view has been rendered in the DOM so + // attachments get proper width applied. + _.defer( this.css, this ); }, dispose: function() { @@ -5531,7 +5577,10 @@ AttachmentView: media.view.Attachment.Library }); + this.listenTo( this.controller, 'show:upload:attachment', _.bind( this.showUploader, this ) ); this.createToolbar(); + this.createUploader(); + this.createAttachments(); this.updateContent(); if ( this.options.sidebar ) { this.createSidebar(); @@ -5568,7 +5617,7 @@ // 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 ( _.contains( this.controller.options.mode, 'grid' ) ) { + if ( this.controller.activeModes.where( { id: 'grid' } ).length ) { LibraryViewSwitcher = media.View.extend({ className: 'view-switch media-grid-view-switch', template: media.template( 'media-library-view-switcher') @@ -5578,6 +5627,18 @@ priority: -90 }).render() ); + this.toolbar.set( 'bulkSelectionToggleButton', new media.view.BulkSelectionToggleButton({ + text: 'Bulk Edit', + controller: this.controller, + priority: -70 + }).render() ); + + this.toolbar.set( 'BulkDeleteButton', new media.view.BulkDeleteButton({ + text: 'Bulk Delete', + controller: this.controller, + priority: -69 + }).render() ); + this.toolbar.set( 'gridFieldOptions', new media.view.GridFieldOptions({ controller: this.controller, priority: -50 @@ -5635,48 +5696,38 @@ updateContent: function() { var view = this; - - if( ! this.attachments ) { - this.createAttachments(); - } - if ( ! this.collection.length ) { this.toolbar.get( 'spinner' ).show(); this.collection.more().done(function() { if ( ! view.collection.length ) { - view.createUploader(); + view.attachmentsNoResults.$el.removeClass( 'hidden' ); + } else { + view.attachmentsNoResults.$el.addClass( 'hidden' ); } view.toolbar.get( 'spinner' ).hide(); }); } else { + this.attachmentsNoResults.$el.addClass( 'hidden' ); view.toolbar.get( 'spinner' ).hide(); } }, - removeContent: function() { - _.each(['attachments','uploader'], function( key ) { - if ( this[ key ] ) { - this[ key ].remove(); - delete this[ key ]; - } - }, this ); - }, - createUploader: function() { - this.removeContent(); - this.uploader = new media.view.UploaderInline({ controller: this.controller, status: false, message: l10n.noItemsFound }); + this.uploader.$el.addClass( 'hidden' ); this.views.add( this.uploader ); }, - createAttachments: function() { - this.removeContent(); + showUploader: function() { + this.uploader.$el.removeClass( 'hidden' ); + }, + createAttachments: function() { this.attachments = new media.view.Attachments({ controller: this.controller, collection: this.collection, @@ -5690,6 +5741,17 @@ }); this.views.add( this.attachments ); + + this.attachmentsNoResults = new media.View({ + controller: this.controller + }); + + this.attachmentsNoResults.$el.addClass( 'hidden' ); + this.attachmentsNoResults.$el.html( 'No media found.' ); + + this.views.add( this.attachmentsNoResults ); + + }, createSidebar: function() { diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php index 098a03a543..191287522c 100644 --- a/src/wp-includes/media-template.php +++ b/src/wp-includes/media-template.php @@ -276,7 +276,6 @@ function wp_print_media_templates() {
-