diff --git a/wp-admin/includes/meta-boxes.php b/wp-admin/includes/meta-boxes.php
index f099627336..2aee5aa2d0 100644
--- a/wp-admin/includes/meta-boxes.php
+++ b/wp-admin/includes/meta-boxes.php
@@ -1018,7 +1018,7 @@ function post_thumbnail_meta_box( $post ) {
var $element = $('#select-featured-image'),
$thumbnailId = $element.find('input[name="thumbnail_id"]'),
title = '',
- workflow, setFeaturedImage;
+ workflow, selection, setFeaturedImage;
setFeaturedImage = function( thumbnailId ) {
$element.find('img').remove();
@@ -1037,7 +1037,9 @@ function post_thumbnail_meta_box( $post ) {
}
});
- workflow.selection.on( 'add', function( model ) {
+ selection = workflow.state().get('selection');
+
+ selection.on( 'add', function( model ) {
var sizes = model.get('sizes'),
size;
@@ -1051,8 +1053,8 @@ function post_thumbnail_meta_box( $post ) {
// data besides just calling toJSON().
size = size || model.toJSON();
- workflow.modal.close();
- workflow.selection.clear();
+ workflow.close();
+ selection.clear();
$( '', {
src: size.url,
@@ -1061,7 +1063,7 @@ function post_thumbnail_meta_box( $post ) {
});
}
- workflow.modal.open();
+ workflow.open();
});
$element.on( 'click', '.remove', function( event ) {
diff --git a/wp-admin/js/media-upload.js b/wp-admin/js/media-upload.js
index 8dbc526533..cf46007868 100644
--- a/wp-admin/js/media-upload.js
+++ b/wp-admin/js/media-upload.js
@@ -104,11 +104,10 @@ var tb_position;
workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
title: wp.media.view.l10n.insertMedia,
- multiple: true,
- describe: true
+ multiple: true
} ) );
- workflow.on( 'update:insert', function( selection ) {
+ workflow.get('library').on( 'insert', function( selection ) {
this.insert( selection.map( function( attachment ) {
if ( 'image' === attachment.get('type') )
return wp.media.string.image( attachment ) + ' ';
@@ -117,7 +116,7 @@ var tb_position;
}).join('') );
}, this );
- workflow.on( 'update:gallery', function( selection ) {
+ workflow.get('gallery').on( 'update', function( selection ) {
var view = wp.mce.view.get('gallery'),
shortcode;
diff --git a/wp-includes/css/media-views.css b/wp-includes/css/media-views.css
index 421c262ade..0f22658c48 100644
--- a/wp-includes/css/media-views.css
+++ b/wp-includes/css/media-views.css
@@ -75,6 +75,7 @@
position: relative;
z-index: 50;
height: 60px;
+ padding: 0 10px;
border-bottom: 1px solid #dfdfdf;
}
@@ -101,64 +102,11 @@
}
/**
- * Workspace
+ * Frame
*/
-.media-workspace {
- position: relative;
- width: 100%;
- height: 100%;
-}
-.upload-attachments {
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- width: 180px;
- margin: 10px;
- text-align: center;
- border: 3px dashed #dfdfdf;
- background: #fff;
- z-index: 100;
-}
-
-.upload-attachments h3 {
- font-size: 18px;
- font-weight: 200;
- color: #777;
- padding: 40px 0 0;
- margin: 0;
-}
-
-.upload-attachments span {
- display: block;
- color: #777;
- margin: 10px 0;
-}
-
-.upload-attachments a {
- display: inline-block;
- margin: 0 auto;
-}
-
-.drag-over .upload-attachments {
- width: auto;
- right: 0;
- border-color: #83B4D8;
- box-shadow: 0 0 0 10px #fff;
-}
-
-.existing-attachments {
- position: absolute;
- top: 0;
- left: 200px;
- right: 0;
- bottom: 0;
- margin: 0 20px;
-}
-
-.media-workspace .attachments,
-.media-workspace .media-toolbar {
+.media-frame .attachments,
+.media-frame .media-toolbar {
-webkit-transition-property: left, right, top, bottom, margin;
-moz-transition-property: left, right, top, bottom, margin;
-ms-transition-property: left, right, top, bottom, margin;
@@ -172,9 +120,9 @@
transition-duration: 0.2s;
}
-.media-workspace .attachments {
+.media-frame .attachments {
position: absolute;
- top: 0;
+ top: 61px;
left: 0;
right: 0;
bottom: 0;
@@ -182,22 +130,21 @@
width: auto;
}
-.media-workspace.with-toolbar .attachments {
- top: 61px;
+.media-frame.hide-toolbar .attachments {
+ top: 0;
}
-.media-workspace .media-toolbar {
- margin-top: -61px;
-}
-
-.media-workspace.with-toolbar .media-toolbar {
+.media-frame .media-toolbar {
margin-top: 0;
}
-.media-workspace .media-toolbar .add-to-gallery {
- display: none;
+.media-frame.hide-toolbar .media-toolbar {
+ margin-top: -61px;
}
+.media-frame .media-toolbar .add-to-gallery {
+ display: none;
+}
/**
* Attachments
*/
@@ -213,6 +160,7 @@
left: 0;
right: 0;
height: 50px;
+ padding: 0 10px;
background: #fff;
}
@@ -242,7 +190,7 @@
right: 0;
bottom: 0;
overflow: auto;
- margin: 0 -10px 20px;
+ margin: 0 0 20px;
}
/**
@@ -445,12 +393,70 @@
margin: -8px 0 0 -4px;
}
-.upload-attachments .media-progress-bar {
- margin-top: 80px;
+.uploader-window {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba( 0, 86, 132, 0.9 );
+
+ /*z-index: -200;*/
+ z-index: 250000;
display: none;
+ text-align: center;
+ opacity: 0;
+
+ -webkit-transition: opacity 250ms;
+ -moz-transition: opacity 250ms;
+ -ms-transition: opacity 250ms;
+ -o-transition: opacity 250ms;
+ transition: opacity 250ms;
}
-.uploading .upload-attachments .media-progress-bar {
+/*.drag-over .uploader-window {
+ z-index: 250000;
+}*/
+
+.uploader-window-content {
+ position: absolute;
+ top: 30px;
+ left: 30px;
+ right: 30px;
+ bottom: 30px;
+ border: 1px dashed #fff;
+}
+
+.uploader-window h3 {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ right: 0;
+ -webkit-transform: translateY( -50% );
+ -moz-transform: translateY( -50% );
+ -ms-transform: translateY( -50% );
+ -o-transform: translateY( -50% );
+ transform: translateY( -50% );
+
+ font-size: 18px;
+ font-weight: 200;
+ color: #fff;
+ padding: 0;
+}
+
+.uploader-window .media-progress-bar {
+ margin-top: 20px;
+ max-width: 300px;
+ background: transparent;
+ border-color: #fff;
+ /*display: none;*/
+}
+
+.uploader-window .media-progress-bar div {
+ background: #fff;
+}
+
+.uploading .uploader-window .media-progress-bar {
display: block;
}
diff --git a/wp-includes/js/mce-view.js b/wp-includes/js/mce-view.js
index 2cd6e3846a..6c6c5815df 100644
--- a/wp-includes/js/mce-view.js
+++ b/wp-includes/js/mce-view.js
@@ -692,12 +692,11 @@ window.wp = window.wp || {};
return;
this.workflow = wp.media({
- view: 'gallery',
+ state: 'gallery',
selection: this.attachments.models,
title: mceview.l10n.editGallery,
editing: true,
- multiple: true,
- describe: true
+ multiple: true
});
// Create a single-use workflow. If the workflow is closed,
diff --git a/wp-includes/js/media-models.js b/wp-includes/js/media-models.js
index c64c4e655a..35801e3917 100644
--- a/wp-includes/js/media-models.js
+++ b/wp-includes/js/media-models.js
@@ -14,8 +14,8 @@ window.wp = window.wp || {};
* @return {object} A media workflow.
*/
media = wp.media = function( attributes ) {
- if ( media.controller.Workflow )
- return new media.controller.Workflow( attributes ).attach().render();
+ if ( media.view.Frame )
+ return new media.view.Frame( attributes ).render().attach().open();
};
_.extend( media, { model: {}, view: {}, controller: {} });
diff --git a/wp-includes/js/media-views.js b/wp-includes/js/media-views.js
index a16b6f8ad4..c994d76119 100644
--- a/wp-includes/js/media-views.js
+++ b/wp-includes/js/media-views.js
@@ -8,6 +8,44 @@
// Link any localized strings.
l10n = media.view.l10n = _.isUndefined( _wpMediaViewsL10n ) ? {} : _wpMediaViewsL10n;
+ // Check if the browser supports CSS 3.0 transitions
+ $.support.transition = (function(){
+ var style = document.documentElement.style,
+ transitions = {
+ WebkitTransition: 'webkitTransitionEnd',
+ MozTransition: 'transitionend',
+ OTransition: 'oTransitionEnd otransitionend',
+ transition: 'transitionend'
+ }, transition;
+
+ transition = _.find( _.keys( transitions ), function( transition ) {
+ return ! _.isUndefined( style[ transition ] );
+ });
+
+ return transition && {
+ end: transitions[ transition ]
+ };
+ }());
+
+ // Makes it easier to bind events using transitions.
+ media.transition = function( selector ) {
+ var deferred = $.Deferred();
+
+ if ( $.support.transition ) {
+ if ( ! (selector instanceof $) )
+ selector = $( selector );
+
+ // Resolve the deferred when the first element finishes animating.
+ selector.first().one( $.support.transition.end, deferred.resolve );
+
+ // Otherwise, execute on the spot.
+ } else {
+ deferred.resolve();
+ }
+
+ return deferred.promise();
+ };
+
/**
* ========================================================================
* CONTROLLERS
@@ -15,135 +53,227 @@
*/
/**
- * wp.media.controller.Workflow
+ * wp.media.controller.StateMachine
*/
- media.controller.Workflow = Backbone.Model.extend({
+ media.controller.StateMachine = function( states ) {
+ this.states = new Backbone.Collection( states );
+ };
+
+ // Use Backbone's self-propagating `extend` inheritance method.
+ media.controller.StateMachine.extend = Backbone.Model.extend;
+
+ _.extend( media.controller.StateMachine.prototype, {
+ // Fetch a state model.
+ //
+ // Implicitly creates states.
+ get: function( id ) {
+ // Ensure that the `states` collection exists so the `StateMachine`
+ // can be used as a mixin.
+ this.states = this.states || new Backbone.Collection();
+
+ if ( ! this.states.get( id ) )
+ this.states.add({ id: id });
+ return this.states.get( id );
+ },
+
+ // Selects or returns the active state.
+ //
+ // If a `id` is provided, sets that as the current state.
+ // If no parameters are provided, returns the current state object.
+ state: function( id ) {
+ var previous;
+
+ if ( id ) {
+ if ( previous = this.state() )
+ previous.trigger('deactivate');
+ this._state = id;
+ return this.state().trigger('activate');
+ }
+
+ if ( this._state )
+ return this.get( this._state );
+ }
+ });
+
+ // Map methods from the `states` collection to the `StateMachine` itself.
+ _.each([ 'on', 'off', 'trigger' ], function( method ) {
+ media.controller.StateMachine.prototype[ method ] = function() {
+ // Ensure that the `states` collection exists so the `StateMachine`
+ // can be used as a mixin.
+ this.states = this.states || new Backbone.Collection();
+ // Forward the method to the `states` collection.
+ this.states[ method ].apply( this.states, arguments );
+ return this;
+ };
+ });
+
+ // wp.media.controller.Library
+ // ---------------------------
+ media.controller.Library = Backbone.Model.extend({
defaults: {
- title: '',
- multiple: false,
- view: 'library',
- library: {},
- selection: []
+ id: 'library',
+ multiple: false,
+ describe: false
},
initialize: function() {
+ if ( ! this.get('selection') )
+ this.set( 'selection', new Attachments() );
+
+ if ( ! this.get('library') )
+ this.set( 'library', media.query() );
+
+ this.on( 'activate', this.activate, this );
+ },
+
+ activate: function() {
+ var frame = this.frame,
+ toolbar;
+
+ toolbar = this._postLibraryToolbar = new media.view.Toolbar.PostLibrary({
+ controller: frame,
+ selection: this.get('selection')
+ });
+
+ frame.toolbar( toolbar );
+ this.get('selection').on( 'add remove', toolbar.visibility, toolbar );
+
+ frame.content( new media.view.Attachments({
+ directions: this.get('multiple') ? l10n.selectMediaMultiple : l10n.selectMediaSingular,
+ controller: frame,
+ collection: this.get('library'),
+ // The single `Attachment` view to be used in the `Attachments` view.
+ 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,
+ // automatically select any uploading attachments.
+ if ( this.get('multiple') )
+ wp.Uploader.queue.on( 'add', this.selectUpload, this );
+ },
+
+ deactivate: function() {
+ var toolbar = this._postLibraryToolbar;
+
+ wp.Uploader.queue.off( 'add', this.selectUpload, this );
+ this.get('selection').off( 'add remove', toolbar.visibility, toolbar );
+ },
+
+ selectUpload: function( attachment ) {
+ this.get('selection').add( attachment );
+ }
+ });
+
+ // wp.media.controller.Gallery
+ // ---------------------------
+ media.controller.Gallery = Backbone.Model.extend({
+ defaults: {
+ id: 'gallery',
+ multiple: true,
+ describe: true
+ },
+
+ initialize: function() {
+ if ( ! this.get('selection') )
+ this.set( 'selection', new Attachments() );
+
+ this.on( 'activate', this.activate, this );
+ },
+
+ activate: function() {
+ var frame = this.frame;
+
+ frame.toolbar( new media.view.Toolbar.Gallery({
+ controller: frame,
+ editing: this.get('editing'),
+ selection: this.get('selection')
+ }) );
+
+ frame.content( new media.view.Attachments({
+ directions: 'Gallery time!',
+ controller: frame,
+ collection: this.get('selection'),
+ sortable: true,
+ // The single `Attachment` view to be used in the `Attachments` view.
+ AttachmentView: media.view.Attachment.Gallery
+ }).render() );
+
+ // Automatically select any uploading attachments.
+ wp.Uploader.queue.on( 'add', this.selectUpload, this );
+ },
+
+ deactivate: function() {
+ wp.Uploader.queue.off( 'add', this.selectUpload, this );
+ },
+
+ selectUpload: function( attachment ) {
+ this.get('selection').add( attachment );
+ }
+ });
+
+ /**
+ * ========================================================================
+ * VIEWS
+ * ========================================================================
+ */
+
+ /**
+ * wp.media.view.Frame
+ */
+ media.view.Frame = Backbone.View.extend({
+ tagName: 'div',
+ className: 'media-frame',
+ template: media.template('media-frame'),
+
+ initialize: function() {
+ _.defaults( this.options, {
+ state: 'library',
+ title: '',
+ selection: [],
+ library: {},
+ modal: true,
+ multiple: false,
+ uploader: true
+ });
+
this.createSelection();
-
- // Initialize view storage.
- this._views = {};
- this._pendingViews = {};
-
- // Initialize modal container view.
- this.modal = new media.view.Modal({ controller: this });
-
- // Add default views.
- //
- // Use the `library` property to initialize the corresponding view,
- // then unset the property.
- this.add( 'library', media.view.Workspace.Library, {
- collection: media.query( this.get('library') )
- });
- this.unset('library');
-
- // Add the gallery view.
- this.add( 'gallery', media.view.Workspace.Gallery, {
- collection: this.selection
- });
- this.add( 'gallery-library', media.view.Workspace.Library.Gallery, {
- collection: media.query({ type: 'image' })
- });
+ this.createSubviews();
+ this.createStates();
},
+ render: function() {
+ var els = [ this.sidebar().el, this.toolbar().el, this.content().el ];
+
+ if ( this.modal )
+ this.modal.render();
+
+ // Detach any views that will be rebound to maintain event bidnings.
+ this.$el.children().filter( els ).detach();
+ this.$el.empty().append( els );
+
+ // Render the window uploader if it exists.
+ if ( this.uploader )
+ this.uploader.render().$el.appendTo( this.$el );
- // Registers a view.
- //
- // `id` is a unique ID for the view relative to the workflow instance.
- // `constructor` is a `Backbone.View` constructor. `options` are the
- // options to be passed when the view is initialized.
- //
- // Triggers the `add` and `add:VIEW_ID` events.
- add: function( id, constructor, options ) {
- this.remove( id );
- this._pendingViews[ id ] = {
- view: constructor,
- options: options
- };
- this.trigger( 'add add:' + id, constructor, options );
return this;
},
- // Returns a registered view instance. If an `id` is not provided,
- // it will return the active view.
- //
- // Lazily instantiates a registered view.
- //
- // Triggers the `init` and `init:VIEW_ID` events.
- view: function( id ) {
- var pending;
-
- id = id || this.get('view');
- pending = this._pendingViews[ id ];
-
- if ( ! this._views[ id ] && pending ) {
- this._views[ id ] = new pending.view( _.extend({ controller: this }, pending.options || {} ) );
- delete this._pendingViews[ id ];
- this.trigger( 'init init:' + id, this._views[ id ] );
- }
-
- return this._views[ id ];
- },
-
- // Unregisters a view from the workflow.
- //
- // Triggers the `remove` and `remove:VIEW_ID` events.
- remove: function( id ) {
- delete this._views[ id ];
- delete this._pendingViews[ id ];
- this.trigger( 'remove remove:' + id );
- return this;
- },
-
- // Renders a view and places it within the modal window.
- // Automatically adds a view if `constructor` is provided.
- render: function( id, constructor, options ) {
- var view;
- id = id || this.get('view');
-
- if ( constructor )
- this.add( id, constructor, options );
-
- view = this.view( id );
-
- if ( ! view )
- return this;
-
- view.render();
- this.modal.content( view );
- return this;
- },
-
- update: function( event ) {
- this.close();
- this.trigger( 'update', this.selection, this );
- this.trigger( 'update:' + event, this.selection, this );
- this.selection.clear();
- },
-
createSelection: function() {
- var controller = this;
+ var controller = this,
+ selection = this.options.selection;
- // Initialize workflow-specific models.
- // Use the `selection` property to initialize the Attachments
- // collection, then unset the property.
- this.selection = new Attachments( this.get('selection') );
- this.unset('selection');
+ if ( ! (selection instanceof Attachments) )
+ selection = this.options.selection = new Attachments( selection );
- _.extend( this.selection, {
+ _.extend( selection, {
// Override the selection's add method.
// If the workflow does not support multiple
// selected attachments, reset the selection.
add: function( models, options ) {
- if ( ! controller.get('multiple') ) {
+ if ( ! controller.state().get('multiple') ) {
models = _.isArray( models ) ? _.first( models ) : models;
this.clear( options );
}
@@ -170,22 +300,93 @@
return !! ( this.getByCid( attachment.cid ) || this.get( attachment.id ) );
}
});
+ },
+
+ createStates: function() {
+ var options = this.options;
+
+ // Create the default `states` collection.
+ this.states = new Backbone.Collection();
+
+ // Ensure states have a reference to the frame.
+ this.states.on( 'add', function( model ) {
+ model.frame = this;
+ }, this );
+
+ // Add the default states.
+ this.states.add([
+ new media.controller.Library({
+ selection: options.selection,
+ collection: media.query( options.library ),
+ multiple: this.options.multiple
+ }),
+ new media.controller.Gallery({
+ selection: options.selection
+ })
+ ]);
+
+ // Set the default state.
+ this.state( options.state );
+ },
+
+ createSubviews: function() {
+ // Initialize a stub view for each subview region.
+ _.each(['toolbar','sidebar','content'], function( subview ) {
+ this[ '_' + subview ] = new Backbone.View({
+ tagName: 'div',
+ className: 'media-' + subview
+ });
+ }, this );
+
+ // Initialize modal container view.
+ if ( this.options.modal ) {
+ this.modal = new media.view.Modal({
+ controller: this,
+ $content: this.$el,
+ title: this.options.title
+ });
+ }
+
+ // Initialize window-wide uploader.
+ if ( this.options.uploader ) {
+ this.uploader = new media.view.UploaderWindow({
+ uploader: {
+ dropzone: this.modal ? this.modal.$el : this.$el
+ }
+ });
+ }
}
});
- // Map modal methods to the workflow.
- _.each(['attach','detach','open','close'], function( method ) {
- media.controller.Workflow.prototype[ method ] = function() {
- this.modal[ method ].apply( this.modal, arguments );
- return this;
+ // Make the `Frame` a `StateMachine`.
+ _.extend( media.view.Frame.prototype, media.controller.StateMachine.prototype );
+
+ // Create methods to fetch and replace individual subviews.
+ _.each(['toolbar','sidebar','content'], function( subview ) {
+ media.view.Frame.prototype[ subview ] = function( view ) {
+ var previous = this[ '_' + subview ];
+
+ if ( ! view )
+ return previous;
+
+ view.$el.addClass( 'media-' + subview );
+
+ if ( previous.destroy )
+ previous.destroy();
+ previous.undelegateEvents();
+ previous.$el.replaceWith( view.$el );
+ this[ '_' + subview ] = view;
};
});
- /**
- * ========================================================================
- * VIEWS
- * ========================================================================
- */
+ // Map some of the modal's methods to the frame.
+ _.each(['open','close','attach','detach'], function( method ) {
+ media.view.Frame.prototype[ method ] = function( view ) {
+ if ( this.modal )
+ this.modal[ method ].apply( this.modal, arguments );
+ return this;
+ };
+ });
/**
* wp.media.view.Modal
@@ -200,10 +401,10 @@
initialize: function() {
this.controller = this.options.controller;
- this.controller.on( 'change:title', this.render, this );
_.defaults( this.options, {
- container: document.body
+ container: document.body,
+ title: ''
});
},
@@ -215,29 +416,37 @@
// `this.$el.html()` from garbage collecting its events.
this.options.$content.detach();
- this.$el.html( this.template( this.controller.toJSON() ) );
- this.$('.media-modal-content').append( this.options.$content );
+ this.$el.html( this.template({
+ title: this.options.title
+ }) );
+
+ this.options.$content.addClass('media-modal-content');
+ this.$('.media-modal').append( this.options.$content );
return this;
},
attach: function() {
this.$el.appendTo( this.options.container );
this.controller.trigger( 'attach', this.controller );
+ return this;
},
detach: function() {
this.$el.detach();
this.controller.trigger( 'detach', this.controller );
+ return this;
},
open: function() {
this.$el.show();
this.controller.trigger( 'open', this.controller );
+ return this;
},
close: function() {
this.$el.hide();
this.controller.trigger( 'close', this.controller );
+ return this;
},
closeHandler: function( event ) {
@@ -256,6 +465,103 @@
}
});
+ // wp.media.view.UploaderWindow
+ // ----------------------------
+ media.view.UploaderWindow = Backbone.View.extend({
+ tagName: 'div',
+ className: 'uploader-window',
+ template: media.template('uploader-window'),
+
+ initialize: function() {
+ var uploader;
+
+ this.controller = this.options.controller;
+
+ uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
+ container: this.$el,
+ dropzone: this.$el,
+ browser: this.$('.upload-attachments a'),
+ params: {}
+ });
+
+ // Track uploading attachments.
+ wp.Uploader.queue.on( 'add remove reset change:percent', this.renderUploadProgress, this );
+
+ if ( uploader.dropzone ) {
+ // Ensure the dropzone is a jQuery collection.
+ if ( ! (uploader.dropzone instanceof $) )
+ uploader.dropzone = $( uploader.dropzone );
+
+ // Attempt to initialize the uploader whenever the dropzone is hovered.
+ uploader.dropzone.one( 'mouseenter dragenter', _.bind( this.maybeInitUploader, this ) );
+ }
+ },
+
+ render: function() {
+ this.maybeInitUploader();
+ this.renderUploadProgress();
+ this.$el.html( this.template( this.options ) );
+ this.$bar = this.$('.upload-attachments .media-progress-bar div');
+ return this;
+ },
+
+ maybeInitUploader: function() {
+ var $id, dropzone;
+
+ // If the uploader already exists or the body isn't in the DOM, bail.
+ if ( this.uploader || ! this.$el.closest('body').length )
+ return;
+
+ $id = $('#post_ID');
+ if ( $id.length )
+ this.options.uploader.params.post_id = $id.val();
+
+ this.uploader = new wp.Uploader( this.options.uploader );
+
+ dropzone = this.uploader.dropzone;
+ dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
+ dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
+ },
+
+ show: function() {
+ var $el = this.$el.show();
+
+ // Ensure that the animation is triggered by waiting until
+ // the transparent element is painted into the DOM.
+ _.defer( function() {
+ $el.css({ opacity: 1 });
+ });
+ },
+
+ hide: function() {
+ var $el = this.$el.css({ opacity: 0 });
+
+ media.transition( $el ).done( function() {
+ // Transition end events are subject to race conditions.
+ // Make sure that the value is set as intended.
+ if ( '0' === $el.css('opacity') )
+ $el.hide();
+ });
+ },
+
+ renderUploadProgress: function() {
+ var queue = wp.Uploader.queue;
+
+ this.$el.toggleClass( 'uploading', !! queue.length );
+
+ if ( ! this.$bar || ! queue.length )
+ return;
+
+ this.$bar.width( ( queue.reduce( function( memo, attachment ) {
+ if ( attachment.get('uploading') )
+ return memo + ( attachment.get('percent') || 0 );
+ else
+ return memo + 100;
+ }, 0 ) / queue.length ) + '%' );
+ }
+ });
+
+
/**
* wp.media.view.Toolbar
*/
@@ -264,16 +570,14 @@
className: 'media-toolbar',
initialize: function() {
- this._views = {};
+ this.controller = this.options.controller;
+
+ this._views = {};
this.$primary = $('