Index: /trunk/wp-admin/includes/meta-boxes.php =================================================================== --- /trunk/wp-admin/includes/meta-boxes.php (revision 22320) +++ /trunk/wp-admin/includes/meta-boxes.php (revision 22321) @@ -1019,5 +1019,6 @@ $thumbnailId = $element.find('input[name="thumbnail_id"]'), title = '', - workflow, selection, setFeaturedImage; + update = '', + frame, selection, setFeaturedImage; setFeaturedImage = function( thumbnailId ) { @@ -1030,6 +1031,6 @@ event.preventDefault(); - if ( ! workflow ) { - workflow = wp.media({ + if ( ! frame ) { + frame = wp.media({ title: title, library: { @@ -1038,31 +1039,42 @@ }); - selection = workflow.state().get('selection'); - - selection.on( 'add', function( model ) { - var sizes = model.get('sizes'), - size; - - setFeaturedImage( model.id ); - - // @todo: might need a size hierarchy equivalent. - if ( sizes ) - size = sizes['post-thumbnail'] || sizes.medium; - - // @todo: Need a better way of accessing full size - // data besides just calling toJSON(). - size = size || model.toJSON(); - - workflow.close(); - selection.clear(); - - $( '', { - src: size.url, - width: size.width - }).prependTo( $element ); - }); + frame.toolbar( new wp.media.view.Toolbar({ + controller: frame, + items: { + update: { + style: 'primary', + text: update, + priority: 40, + + click: function() { + var selection = frame.state().get('selection'), + model = selection.first(), + sizes = model.get('sizes'), + size; + + setFeaturedImage( model.id ); + + // @todo: might need a size hierarchy equivalent. + if ( sizes ) + size = sizes['post-thumbnail'] || sizes.medium; + + // @todo: Need a better way of accessing full size + // data besides just calling toJSON(). + size = size || model.toJSON(); + + frame.close(); + selection.clear(); + + $( '', { + src: size.url, + width: size.width + }).prependTo( $element ); + } + } + } + }) ); } - workflow.open(); + frame.open(); }); Index: /trunk/wp-includes/css/media-views.css =================================================================== --- /trunk/wp-includes/css/media-views.css (revision 22320) +++ /trunk/wp-includes/css/media-views.css (revision 22321) @@ -73,7 +73,10 @@ */ .media-toolbar { - position: relative; - z-index: 50; - height: 60px; + position: absolute; + top: 0; + left: 220px; + right: 0; + z-index: 100; + height: 50px; padding: 0 10px; border-bottom: 1px solid #dfdfdf; @@ -92,5 +95,5 @@ margin-left: 10px; float: left; - margin-top: 16px; + margin-top: 10px; } @@ -99,5 +102,46 @@ margin-right: 10px; float: left; - margin-top: 16px; + margin-top: 10px; +} + +/** + * Sidebar + */ +.media-sidebar { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 219px; + z-index: 50; + background: #f5f5f5; + border-right: 1px solid #dfdfdf; +} + +.hide-sidebar .media-sidebar { + display: none; +} + +.media-sidebar .sidebar-title { + font-weight: 200; + font-size: 20px; + margin: 0; + padding: 12px 10px 10px; + line-height: 28px; + /*border-bottom: 1px solid #dfdfdf;*/ +} + +.media-sidebar .sidebar-content { + padding: 0 10px; +} + +.media-sidebar .search { + display: block; + width: 100%; +} + +.media-sidebar .selection-preview { + display: block; + padding-top: 5px; } @@ -106,6 +150,7 @@ */ -.media-frame .attachments, -.media-frame .media-toolbar { +.media-frame .media-content, +.media-frame .media-toolbar, +.media-frame .media-sidebar { -webkit-transition-property: left, right, top, bottom, margin; -moz-transition-property: left, right, top, bottom, margin; @@ -121,24 +166,17 @@ } -.media-frame .attachments { - position: absolute; - top: 61px; - left: 0; +.media-frame .media-content { + position: absolute; + top: 51px; + left: 220px; right: 0; bottom: 0; height: auto; width: auto; -} - -.media-frame.hide-toolbar .attachments { - top: 0; -} - -.media-frame .media-toolbar { - margin-top: 0; -} - -.media-frame.hide-toolbar .media-toolbar { - margin-top: -61px; + overflow: auto; +} + +.media-frame.hide-sidebar .media-content { + left: 0; } @@ -146,34 +184,9 @@ display: none; } -/** - * Attachments - */ -.attachments { - position: relative; - width: 100%; - height: 100%; -} - -.attachments-header { - position: absolute; - top: 0; - left: 0; - right: 0; - height: 50px; - padding: 0 10px; - background: #fff; -} - -.attachments-header h3 { - float: left; - margin: 0; - padding: 0; - line-height: 50px; - font-size: 18px; - font-weight: 200; -} - -.attachments-header .search { - float: right; + +/** + * Search + */ +.media-frame .search { margin-top: 11px; padding: 4px; @@ -184,4 +197,32 @@ } +/** + * Attachments + */ +/*.attachments { + position: relative; + width: 100%; + height: 100%; +} + +.attachments-header { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 50px; + padding: 0 10px; + background: #fff; +} + +.attachments-header h3 { + float: left; + margin: 0; + padding: 0; + line-height: 50px; + font-size: 18px; + font-weight: 200; +} + .attachments ul { position: absolute; @@ -192,5 +233,5 @@ overflow: auto; margin: 0 0 20px; -} +}*/ /** @@ -402,5 +443,4 @@ background: rgba( 0, 86, 132, 0.9 ); - /*z-index: -200;*/ z-index: 250000; display: none; @@ -414,8 +454,4 @@ transition: opacity 250ms; } - -/*.drag-over .uploader-window { - z-index: 250000; -}*/ .uploader-window-content { Index: /trunk/wp-includes/js/media-views.js =================================================================== --- /trunk/wp-includes/js/media-views.js (revision 22320) +++ /trunk/wp-includes/js/media-views.js (revision 22321) @@ -114,5 +114,6 @@ id: 'library', multiple: false, - describe: false + describe: false, + title: l10n.mediaLibrary }, @@ -131,7 +132,8 @@ toolbar; + // Toolbar. toolbar = this._postLibraryToolbar = new media.view.Toolbar.PostLibrary({ controller: frame, - selection: this.get('selection') + state: this }); @@ -139,6 +141,24 @@ this.get('selection').on( 'add remove', toolbar.visibility, toolbar ); + // Sidebar. + frame.sidebar( new media.view.Sidebar({ + controller: frame, + views: { + search: new media.view.Search({ + controller: frame, + model: this.get('library').props, + priority: 20 + }), + + selection: new media.view.SelectionPreview({ + controller: frame, + collection: this.get('selection'), + priority: 40 + }) + } + }) ); + + // Content. frame.content( new media.view.Attachments({ - directions: this.get('multiple') ? l10n.selectMediaMultiple : l10n.selectMediaSingular, controller: frame, collection: this.get('library'), @@ -146,7 +166,4 @@ AttachmentView: media.view.Attachment.Library }).render() ); - - if ( ! this.get('selection').length ) - frame.$el.addClass('hide-toolbar'); // If we're in a workflow that supports multiple attachments, @@ -174,5 +191,6 @@ id: 'gallery', multiple: true, - describe: true + describe: true, + title: l10n.createGallery }, @@ -187,12 +205,17 @@ var frame = this.frame; + // Toolbar. frame.toolbar( new media.view.Toolbar.Gallery({ controller: frame, - editing: this.get('editing'), - selection: this.get('selection') + state: this }) ); + // Sidebar. + frame.sidebar( new media.view.Sidebar({ + controller: frame + }).render() ); + + // Content. frame.content( new media.view.Attachments({ - directions: 'Gallery time!', controller: frame, collection: this.get('selection'), @@ -246,5 +269,5 @@ render: function() { - var els = [ this.sidebar().el, this.toolbar().el, this.content().el ]; + var els = [ this.toolbar().el, this.sidebar().el, this.content().el ]; if ( this.modal ) @@ -635,14 +658,9 @@ media.view.Toolbar.PostLibrary = media.view.Toolbar.extend({ initialize: function() { - var selection = this.options.selection, + var state = this.options.state, + selection = state.get('selection'), controller = this.options.controller; this.options.items = { - 'selection-preview': new media.view.SelectionPreview({ - controller: controller, - collection: selection, - priority: -40 - }), - 'create-new-gallery': { style: 'primary', @@ -663,5 +681,5 @@ click: function() { controller.close(); - controller.state().trigger( 'insert', selection ); + state.trigger( 'insert', selection ); selection.clear(); } @@ -699,13 +717,13 @@ media.view.Toolbar.prototype.initialize.apply( this, arguments ); + this.visibility(); }, visibility: function() { - var selection = this.options.selection, + var state = this.options.state, + selection = state.get('selection'), controller = this.options.controller, count = selection.length, showGallery; - - controller.$el.toggleClass( 'hide-toolbar', ! count ); // Check if every attachment in the selection is an image. @@ -719,4 +737,6 @@ button.model.set( 'style', showGallery ? '' : 'primary' ); }); + + _.first( insert.buttons ).model.set( 'disabled', ! count ); } }); @@ -726,6 +746,7 @@ media.view.Toolbar.Gallery = media.view.Toolbar.extend({ initialize: function() { - var editing = this.options.editing, - selection = this.options.selection, + var state = this.options.state, + editing = state.get('editing'), + selection = state.get('selection'), controller = this.options.controller; @@ -737,5 +758,5 @@ click: function() { controller.close(); - controller.state().trigger( 'update', selection ); + state.trigger( 'update', selection ); selection.clear(); controller.state('library'); @@ -770,7 +791,8 @@ defaults: { - text: '', - style: '', - size: 'large' + text: '', + style: '', + size: 'large', + disabled: false }, @@ -797,15 +819,17 @@ render: function() { - var classes = [ 'button', this.className ]; - - if ( this.model.get('style') ) - classes.push( 'button-' + this.model.get('style') ); - - if ( this.model.get('size') ) - classes.push( 'button-' + this.model.get('size') ); + var classes = [ 'button', this.className ], + model = this.model.toJSON(); + + if ( model.style ) + classes.push( 'button-' + model.style ); + + if ( model.size ) + classes.push( 'button-' + model.size ); classes = _.uniq( classes.concat( this.options.classes ) ); this.el.className = classes.join(' '); + this.$el.attr( 'disabled', model.disabled ); // Detach the dropdown. @@ -823,5 +847,5 @@ click: function( event ) { event.preventDefault(); - if ( this.options.click ) + if ( this.options.click && ! this.model.get('disabled') ) this.options.click.apply( this, arguments ); } @@ -851,4 +875,68 @@ render: function() { this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); + return this; + } + }); + + /** + * wp.media.view.Sidebar + */ + media.view.Sidebar = Backbone.View.extend({ + tagName: 'div', + className: 'media-sidebar', + template: media.template('sidebar'), + + initialize: function() { + this.controller = this.options.controller; + this._views = {}; + + if ( this.options.views ) + this.add( this.options.views, { silent: true }).render(); + }, + + render: function() { + var els = _( this._views ).chain().sortBy( function( view ) { + return view.options.priority || 10; + }).pluck('el').value(); + + // Make sure to detach the elements we want to reuse. + // Otherwise, `jQuery.html()` will unbind their events. + $( els ).detach(); + + this.$el.html( this.template({ + title: this.controller.state().get('title') || '', + uploader: this.controller.options.uploader + }) ); + + this.$('.sidebar-content').html( els ); + + return this; + }, + + add: function( id, view, options ) { + // Accept an object with an `id` : `view` mapping. + if ( _.isObject( id ) ) { + _.each( id, function( view, id ) { + this.add( id, view, options ); + }, this ); + return this; + } + + view.controller = view.controller || this.controller; + + this._views[ id ] = view; + if ( ! options || ! options.silent ) + this.render(); + return this; + }, + + get: function( id ) { + return this._views[ id ]; + }, + + remove: function( id, options ) { + delete this._views[ id ]; + if ( ! options || ! options.silent ) + this.render(); return this; } @@ -1069,10 +1157,9 @@ */ media.view.Attachments = Backbone.View.extend({ - tagName: 'div', + tagName: 'ul', className: 'attachments', - template: media.template('attachments'), events: { - 'keyup .search': 'search' + 'scroll': 'scroll' }, @@ -1093,11 +1180,8 @@ }, this ); - this.collection.on( 'reset', this.refresh, this ); - - this.$list = $('