Streamlining media, part I.

The main goal here is to rearrange the media components in a modularized structure to support more linear workflows. This is that structure using the pre-existing workflows, which will be improved over the course of the next few commits.

This leaves a few pieces a bit rough around the edges: namely gallery editing and selecting a featured image.

The fine print follows.

----

'''Styles'''
* Tightened padding around the modal to optimize for a smaller default screen size.
* Added a light dashed line surrounding the modal to provide a subtle cue for the persistent dropzone (which is evolving into a power user feature since we now have a dedicated `upload` state).
* Add a size for `hero` buttons.
* Remove transitions from frame subviews (e.g. menu, content, sidebar, toolbar).

----

'''Code'''

`wp.media.controller.StateManager`
* Don't fire `activate` and `deactivate` if attempting to switch to the current state.

`wp.media.controller.State`
* Add a base state class to bind default methods (as not all states will inherit from the `Library` state).
* On `activate`, fire `activate()`, `menu()`, `content()`, `sidebar()`, and `toolbar()`.
* The menu view is often a shared object (as its most common use case is switching between states). Assign the view to the state's `menu` attribute.
* `menu()` automatically fetches the state's `menu` attribute, attaches the menu view to the frame, and attempts to select a menu item that matches the state's `id`.

`wp.media.controller.Library`
* Now inherits from `wp.media.controller.State`.

`wp.media.controller.Upload`
* A new state to improve the upload experience.
* Displays a large dropzone when empty (a `UploaderInline` view).
* When attachments are uploaded, displays management interface (a `library` state restricted to attachments uploaded during the current session).

`wp.media.view.Frame`
* In `menu()`, `content()`, `sidebar()`, and `toolbar()`, only change the view if it differs from the current view. Also, ensure `hide-*` classes are properly removed.
*

`wp.media.view.PriorityList`
* A new container view used to sort and render child views by the `priority` property.
* Used by `wp.media.view.Sidebar` and `wp.media.view.Menu`.
* Next step: Use two instances to power `wp.media.view.Toolbar`.

`wp.media.view.Menu` and `wp.media.view.MenuItem`
* A new `PriorityList` view that renders a list of views used to switch between states.
* `MenuItem` instances have `id` attributes that are tied directly to states.
* Separators can be added as plain `Backbone.View` instances with the `separator` class.
* Supports any type of `Backbone.View`.

`media.view.Menu.Landing`
* The landing menu for the 'insert media' workflow.
* Includes an inactive link to an "Embed from URL" state.
* Next steps: only use in select cases to allot for other workflows (such as featured images).

`wp.media.view.AttachmentsBrowser`
* A container to render an `Attachments` view with accompanying UI controls (similar to what the `Attachments` view was when it contained the `$list` property).
* Currently only renders a `Search` view as a control.
* Next steps: Add optional view counts (e.g. "21 images"), upload buttons, and collection filter UI.

`wp.media.view.Attachments`
* If the `Attachments` scroll buffer is not filled with `Attachment` views, continue loading more attachments.
* Use `this.model` instead of `this.controller.state()` to allow `Attachments` views to have differing `edge` and `gutter` properties.
* Add `edge()`, a method used to calculate the optimal dimensions for an attachment based on the current width of the `Attachments` container element.
* `edge()` is currently only enabled on resize, as the relative positioning and CSS transforms used to center thumbnails are suboptimal when coupled with frequent resizing.
* Next steps: For infinite scroll performance improvements, look into absolutely positioning attachment views and paging groups of attachment views.

`wp.media.view.UploaderWindow`
* Now generates a `$browser` element as the browse button (instead of a full `UploaderInline` view). Using a portable browse button prevents us from having to create a new `wp.Uploader` instance every time we want access to a browse button.

`wp.media.view.UploaderInline`
* No longer directly linked to the `UploaderWindow` view or its `wp.Uploader` instance.
* Used as the default `upload` state view.

`wp.media.view.Selection`
* An interactive representation of the selected `Attachments`.
* Based on the improved workflows, this is likely overkill. For simplicity's sake, will probably remove this in favor of `SelectionPreview`.

----

see #21390.



git-svn-id: http://core.svn.wordpress.org/trunk@22362 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Daryl Koopersmith 2012-11-04 22:59:12 +00:00
parent a29e0190db
commit 92823b8635
5 changed files with 688 additions and 153 deletions

View File

@ -81,6 +81,14 @@ input[type="submit"]::-moz-focus-inner {
padding: 0 8px 1px;
}
.button.button-hero,
.button-group.button-hero .button {
font-size: 14px;
height: 46px;
line-height: 44px;
padding: 0 36px;
}
.button:active {
outline: none;
}
@ -294,7 +302,7 @@ input[type="submit"]::-moz-focus-inner {
position: absolute;
top: 100%;
left: 0;
margin-top: 5px;
margin: 5px 0;
padding: 0.8em 1em;
border-radius: 3px;
@ -314,3 +322,8 @@ input[type="submit"]::-moz-focus-inner {
left: auto;
right: 0;
}
.dropdown-flip-y .dropdown {
top: auto;
bottom: 100%;
}

View File

@ -3,10 +3,10 @@
*/
.media-modal {
position: fixed;
top: 80px;
left: 60px;
right: 60px;
bottom: 60px;
top: 60px;
left: 40px;
right: 40px;
bottom: 40px;
z-index: 125000;
}
@ -21,6 +21,15 @@
z-index: 120000;
}
.media-modal-backdrop div {
position: absolute;
top: 10px;
left: 10px;
right: 10px;
bottom: 10px;
border: 1px dashed rgba( 255, 255, 255, 0.5 );
}
.media-modal-title,
.media-modal-close {
position: absolute;
@ -38,7 +47,7 @@
float: left;
padding: 0;
margin: 0;
font-size: 1.4em;
font-size: 16px;
}
.media-modal-close {
@ -74,12 +83,23 @@
.media-toolbar {
position: absolute;
top: 0;
left: 220px;
left: 0;
right: 0;
z-index: 100;
height: 50px;
padding: 0 10px;
border-bottom: 1px solid #dfdfdf;
height: 60px;
padding: 0 16px;
border: 0 solid #dfdfdf;
}
.media-frame > .media-toolbar {
top: auto;
left: 200px;
bottom: 0;
border-width: 1px 0 0 0;
}
.media-frame.hide-toolbar > .media-toolbar {
bottom: -61px;
}
.media-toolbar-primary {
@ -94,14 +114,14 @@
.media-toolbar-primary > .media-button-group {
margin-left: 10px;
float: left;
margin-top: 10px;
margin-top: 15px;
}
.media-toolbar-secondary > .media-button,
.media-toolbar-secondary > .media-button-group {
margin-right: 10px;
float: left;
margin-top: 10px;
margin-top: 15px;
}
/**
@ -110,16 +130,21 @@
.media-sidebar {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 219px;
z-index: 50;
right: 0;
bottom: 61px;
width: 247px;
padding: 0 16px;
z-index: 75;
background: #f5f5f5;
border-right: 1px solid #dfdfdf;
border-left: 1px solid #dfdfdf;
}
.hide-sidebar .media-sidebar {
display: none;
right: -280px;
}
.hide-toolbar .media-sidebar {
bottom: 0;
}
.media-sidebar .sidebar-title {
@ -146,32 +171,66 @@
padding-top: 5px;
}
/**
* Menu
*/
.media-menu {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 199px;
margin: 0;
padding: 16px 0;
z-index: 200;
box-shadow: inset -6px 0 6px -6px rgba( 0, 0, 0, 0.4 );
}
.media-menu li {
position: relative;
padding: 4px 20px;
margin: 0;
line-height: 18px;
font-size: 14px;
color: #21759B;
text-shadow: 0 1px 0 #fff;
}
.media-menu-item {
cursor: pointer;
}
.media-menu li:hover {
background: rgba( 0, 0, 0, 0.04 );
}
.media-menu .active,
.media-menu .active:hover {
color: #333;
font-weight: bold;
}
.media-menu .separator {
height: 0;
margin: 12px 20px;
padding: 0;
border-top: 1px solid #dfdfdf;
border-bottom: 1px solid #fff;
}
/**
* Frame
*/
.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;
-ms-transition-property: left, right, top, bottom, margin;
-o-transition-property: left, right, top, bottom, margin;
transition-property: left, right, top, bottom, margin;
-webkit-transition-duration: 0.2s;
-moz-transition-duration: 0.2s;
-ms-transition-duration: 0.2s;
-o-transition-duration: 0.2s;
transition-duration: 0.2s;
.media-frame {
overflow: hidden;
}
.media-frame .media-content {
position: absolute;
top: 51px;
left: 220px;
right: 0;
bottom: 0;
top: 0;
left: 200px;
right: 280px;
bottom: 61px;
height: auto;
width: auto;
margin: 0;
@ -179,7 +238,11 @@
}
.media-frame.hide-sidebar .media-content {
left: 0;
right: 0;
}
.media-frame.hide-toolbar .media-content {
bottom: 0;
}
.media-frame .media-toolbar .add-to-gallery {
@ -198,6 +261,14 @@
font-family: sans-serif;
}
/**
* Attachments
*/
.attachments {
margin: 0;
padding-right: 16px;
}
/**
* Attachment
*/
@ -388,6 +459,28 @@
border-radius: 0;
}
/**
* Attachments Browser
*/
.media-frame .attachments-browser {
overflow: hidden;
}
.attachments-browser .media-toolbar {
height: 50px;
}
.attachments-browser .attachments {
position: absolute;
top: 50px;
left: 0;
right: 0;
bottom: 0;
overflow: auto;
}
/**
* Progress Bar
*/
@ -445,10 +538,10 @@
.uploader-window-content {
position: absolute;
top: 30px;
left: 30px;
right: 30px;
bottom: 30px;
top: 10px;
left: 10px;
right: 10px;
bottom: 10px;
border: 1px dashed #fff;
}
@ -463,7 +556,7 @@
-o-transform: translateY( -50% );
transform: translateY( -50% );
font-size: 18px;
font-size: 20px;
font-weight: 200;
color: #fff;
padding: 0;
@ -485,8 +578,24 @@
display: block;
}
.uploader-inline {
display: none;
.media-content.uploader-inline {
margin: 20px;
padding: 20px;
border: 1px dashed #aaa;
text-align: center;
}
.uploader-inline-content {
position: absolute;
top: 30%;
left: 0;
right: 0;
}
.uploader-inline h3 {
font-size: 20px;
font-weight: 200;
margin-bottom: 1.6em;
}
.uploader-inline .media-progress-bar {
@ -497,23 +606,85 @@
display: block;
}
.media-sidebar .uploader-inline {
display: block;
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 100px;
margin: 10px;
padding-top: 10px;
text-align: center;
border: 1px dashed #aaa;
.uploader-inline .browser {
display: inline-block !important;
}
.media-sidebar .uploader-inline h3 {
font-weight: 200;
font-size: 16px;
margin: 10px 0;
/**
* Selection
*/
.media-selection {
position: absolute;
top: 0;
left: 0;
right: 350px;
height: 60px;
padding: 0 0 0 16px;
overflow: hidden;
white-space: nowrap;
}
.media-selection .selection-info {
display: inline-block;
height: 60px;
margin-right: 10px;
vertical-align: top;
}
.media-selection.empty {
display: none;
}
.media-selection .count {
display: block;
padding-top: 12px;
font-size: 14px;
line-height: 20px;
font-weight: bold;
}
.media-selection .clear-selection {
display: block;
text-decoration: none;
line-height: 16px;
}
.media-selection .attachments {
display: inline-block;
height: 60px;
margin-top: 5px;
overflow: hidden;
vertical-align: top;
}
.media-selection .selected.attachment {
box-shadow: none;
}
.media-selection .details.attachment {
box-shadow:
0 0 0 1px #fff,
0 0 0 3px #1e8cbe;
}
.media-selection:after {
content: '';
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 25px;
background-image: -webkit-gradient(linear, right top, right top, from( rgba( 255, 255, 255, 1 ) ), to( rgba( 255, 255, 255, 0 ) ));
background-image: -webkit-linear-gradient(right, rgba( 255, 255, 255, 1 ) , rgba( 255, 255, 255, 0 ) );
background-image: -moz-linear-gradient(right, rgba( 255, 255, 255, 1 ) , rgba( 255, 255, 255, 0 ) );
background-image: -o-linear-gradient(right, rgba( 255, 255, 255, 1 ) , rgba( 255, 255, 255, 0 ) );
background-image: linear-gradient(to left, rgba( 255, 255, 255, 1 ) , rgba( 255, 255, 255, 0 ) );
}
.media-selection .attachment .filename {
display: none;
}
/**
@ -584,10 +755,6 @@
* Attachment Details
*/
.attachment-details {
padding-top: 20px;
}
.attachment-details-preview {
cursor: default;
}

View File

@ -83,15 +83,21 @@
state: function( id ) {
var previous;
if ( id ) {
if ( previous = this.state() )
previous.trigger('deactivate');
this._state = id;
return this.state().trigger('activate');
}
if ( ! id )
return this._state ? this.get( this._state ) : null;
if ( this._state )
return this.get( this._state );
previous = this.state();
// Bail if we're trying to select the current state, or a state
// that does not exist.
if ( previous && id === previous.id || ! this.states.get( id ) )
return;
if ( previous )
previous.trigger('deactivate');
this._state = id;
this.state().trigger('activate');
}
});
@ -107,9 +113,50 @@
};
});
// wp.media.controller.State
// ---------------------------
media.controller.State = Backbone.Model.extend({
initialize: function() {
this.on( 'activate', this._activate, this );
this.on( 'activate', this.activate, this );
this.on( 'deactivate', this._deactivate, this );
this.on( 'deactivate', this.deactivate, this );
},
activate: function() {},
_activate: function() {
this.active = true;
this.menu();
this.toolbar();
this.sidebar();
this.content();
},
deactivate: function() {},
_deactivate: function() {
this.active = false;
},
menu: function() {
var menu = this.get('menu');
if ( ! menu )
return;
this.frame.menu( menu );
menu.select( this.id );
},
toolbar: function() {},
sidebar: function() {},
content: function() {}
});
// wp.media.controller.Library
// ---------------------------
media.controller.Library = Backbone.Model.extend({
media.controller.Library = media.controller.State.extend({
defaults: {
id: 'library',
multiple: false,
@ -133,15 +180,10 @@
if ( ! this.get('gutter') )
this.set( 'gutter', 8 );
this.on( 'activate', this.activate, this );
this.on( 'deactivate', this.deactivate, this );
media.controller.State.prototype.initialize.apply( this, arguments );
},
activate: function() {
this.toolbar();
this.sidebar();
this.content();
// If we're in a workflow that supports multiple attachments,
// automatically select any uploading attachments.
if ( this.get('multiple') )
@ -153,6 +195,7 @@
deactivate: function() {
var toolbar = this._postLibraryToolbar;
if ( toolbar )
this.get('selection').off( 'add remove', toolbar.visibility, toolbar );
@ -184,30 +227,16 @@
}) );
this.details();
frame.sidebar().add({
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: function() {
var frame = this.frame;
// Content.
frame.content( new media.view.Attachments({
frame.content( new media.view.AttachmentsBrowser({
controller: frame,
collection: this.get('library'),
// The single `Attachment` view to be used in the `Attachments` view.
AttachmentView: media.view.Attachment.Library
model: this
}).render() );
},
@ -255,6 +284,65 @@
}
});
// wp.media.controller.Upload
// ---------------------------
media.controller.Upload = media.controller.Library.extend({
defaults: _.defaults({
id: 'upload'
}, media.controller.Library.prototype.defaults ),
initialize: function() {
var library = this.get('library');
// If a `library` attribute isn't provided, create a new
// `Attachments` collection that observes (and thereby receives
// all uploading) attachments.
if ( ! library ) {
library = new Attachments();
library.props.set({
orderby: 'date',
order: 'ASC'
});
library.observe( wp.Uploader.queue );
this.set( 'library', library );
}
media.controller.Library.prototype.initialize.apply( this, arguments );
},
activate: function() {
this.get('library').on( 'add remove reset', this.refresh, this );
media.controller.Library.prototype.activate.apply( this, arguments );
this.refresh();
},
deactivate: function() {
this.get('library').off( 'add remove reset', this.refresh, this );
media.controller.Library.prototype.deactivate.apply( this, arguments );
},
refresh: function() {
this.frame.$el.toggleClass( 'hide-sidebar hide-toolbar', ! this.get('library').length );
this.content();
},
content: function() {
var frame = this.frame,
upload;
if ( this.get('library').length ) {
media.controller.Library.prototype.content.apply( this, arguments );
} else {
upload = new media.view.UploaderInline({
controller: frame
}).render();
frame.content( upload );
}
}
});
// wp.media.controller.Gallery
// ---------------------------
media.controller.Gallery = media.controller.Library.extend({
@ -296,6 +384,7 @@
this.frame.content( new media.view.Attachments({
controller: this.frame,
collection: this.get('library'),
model: this,
sortable: true,
// The single `Attachment` view to be used in the `Attachments` view.
AttachmentView: media.view.Attachment.Gallery
@ -345,7 +434,7 @@
initialize: function() {
_.defaults( this.options, {
state: 'library',
state: 'upload',
title: '',
selection: [],
library: {},
@ -361,12 +450,12 @@
},
render: function() {
var els = [ this.toolbar().el, this.sidebar().el, this.content().el ];
var els = [ this.menu().el, this.content().el, this.sidebar().el, this.toolbar().el ];
if ( this.modal )
this.modal.render();
// Detach any views that will be rebound to maintain event bidnings.
// Detach any views that will be rebound to maintain event bindings.
this.$el.children().filter( els ).detach();
this.$el.empty().append( els );
@ -389,7 +478,12 @@
},
createStates: function() {
var options = this.options;
var options = this.options,
menus = {
landing: new media.view.Menu.Landing({
controller: this
})
};
// Create the default `states` collection.
this.states = new Backbone.Collection();
@ -404,7 +498,12 @@
new media.controller.Library({
selection: options.selection,
library: media.query( options.library ),
multiple: this.options.multiple
multiple: this.options.multiple,
menu: menus.landing
}),
new media.controller.Upload({
multiple: this.options.multiple,
menu: menus.landing
}),
new media.controller.Gallery({
library: options.selection,
@ -419,7 +518,7 @@
createSubviews: function() {
// Initialize a stub view for each subview region.
_.each(['toolbar','sidebar','content'], function( subview ) {
_.each(['menu','content','sidebar','toolbar'], function( subview ) {
this[ '_' + subview ] = new Backbone.View({
tagName: 'div',
className: 'media-' + subview
@ -450,15 +549,21 @@
_.extend( media.view.Frame.prototype, media.controller.StateMachine.prototype );
// Create methods to fetch and replace individual subviews.
_.each(['toolbar','sidebar','content'], function( subview ) {
_.each(['menu','content','sidebar','toolbar'], function( subview ) {
media.view.Frame.prototype[ subview ] = function( view ) {
var previous = this[ '_' + subview ];
if ( ! view )
return previous;
if ( view === previous )
return;
view.$el.addClass( 'media-' + subview );
// Remove the hide class.
this.$el.removeClass( 'hide-' + subview );
if ( previous.destroy )
previous.destroy();
previous.undelegateEvents();
@ -564,17 +669,12 @@
var uploader;
this.controller = this.options.controller;
this.inline = new media.view.UploaderInline({
controller: this.controller,
uploaderWindow: this
}).render();
this.inline.$el.appendTo('body');
this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
container: this.inline.$el,
dropzone: this.$el,
browser: this.inline.$('.browser'),
browser: this.$browser,
params: {}
});
@ -647,6 +747,9 @@
initialize: function() {
this.controller = this.options.controller;
if ( ! this.options.$browser )
this.options.$browser = this.controller.uploader.$browser;
// Track uploading attachments.
wp.Uploader.queue.on( 'add remove reset change:percent', this.renderUploadProgress, this );
},
@ -656,8 +759,17 @@
},
render: function() {
var $browser = this.options.$browser,
$placeholder;
this.renderUploadProgress();
this.$el.html( this.template( this.options ) );
$placeholder = this.$('.browser');
$browser.text( $placeholder.text() );
$browser[0].className = $placeholder[0].className;
$placeholder.replaceWith( $browser.show() );
this.$bar = this.$('.media-progress-bar div');
return this;
},
@ -679,7 +791,6 @@
}
});
/**
* wp.media.view.Toolbar
*/
@ -765,6 +876,12 @@
controller = this.options.controller;
this.options.items = {
selection: new media.view.Selection({
controller: controller,
collection: selection,
priority: -40
}).render(),
'create-new-gallery': {
style: 'primary',
text: l10n.createNewGallery,
@ -777,7 +894,7 @@
'insert-into-post': new media.view.ButtonGroup({
priority: 30,
classes: 'dropdown-flip-x',
classes: 'dropdown-flip-x dropdown-flip-y',
buttons: [
{
text: l10n.insertIntoPost,
@ -1033,19 +1150,19 @@
});
/**
* wp.media.view.Sidebar
* wp.media.view.PriorityList
*/
media.view.Sidebar = Backbone.View.extend({
media.view.PriorityList = 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 });
this.add( _.extend( {}, this.views, this.options.views ), { silent: true });
delete this.views;
delete this.options.views;
if ( ! this.options.silent )
this.render();
@ -1060,18 +1177,7 @@
// 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 );
if ( this.controller.uploader ) {
this.$el.append( this.controller.uploader.inline.$el );
this.controller.uploader.refresh();
}
this.$el.html( els );
return this;
},
@ -1089,6 +1195,9 @@
return this;
}
if ( ! (view instanceof Backbone.View) )
view = this.toView( view, id, options );
view.controller = view.controller || this.controller;
this._views[ id ] = view;
@ -1106,9 +1215,94 @@
if ( ! options || ! options.silent )
this.render();
return this;
},
toView: function( options ) {
return new Backbone.View( options );
}
});
/**
* wp.media.view.Menu
*/
media.view.Menu = media.view.PriorityList.extend({
tagName: 'ul',
className: 'media-menu',
toView: function( options, id ) {
options = options || {};
options.id = id;
return new media.view.MenuItem( options ).render();
},
select: function( id ) {
var view = this.get( id );
if ( ! view )
return;
this.deselect();
view.$el.addClass('active');
},
deselect: function() {
this.$el.children().removeClass('active');
}
});
media.view.MenuItem = Backbone.View.extend({
tagName: 'li',
className: 'media-menu-item',
events: {
'click': 'toState'
},
toState: function() {
this.controller.state( this.options.id );
},
render: function() {
var options = this.options;
if ( options.text )
this.$el.text( options.text );
else if ( options.html )
this.$el.html( options.html );
return this;
}
});
media.view.Menu.Landing = media.view.Menu.extend({
views: {
upload: {
text: l10n.uploadFilesTitle,
priority: 20
},
library: {
text: l10n.mediaLibraryTitle,
priority: 40
},
separateLibrary: new Backbone.View({
className: 'separator',
priority: 60
}),
embed: {
text: l10n.embedFromUrlTitle,
priority: 80
}
}
});
/**
* wp.media.view.Sidebar
*/
media.view.Sidebar = media.view.PriorityList.extend({
className: 'media-sidebar'
});
/**
* wp.media.view.Attachment
*/
@ -1118,7 +1312,7 @@
template: media.template('attachment'),
events: {
'click .attachment-preview': 'toggleSelection',
'mousedown .attachment-preview': 'toggleSelection',
'change .describe': 'describe'
},
@ -1322,29 +1516,47 @@
this.initSortable();
this.controller.state().on( 'change:edge change:gutter', this.css, this );
_.bindAll( this, 'css' );
this.model.on( 'change:edge change:gutter', this.css, this );
this._resizeCss = _.debounce( _.bind( this.css, this ), this.refreshSensitivity );
$(window).on( 'resize.attachments', this._resizeCss );
this.css();
},
destroy: function() {
this.collection.off( 'add remove reset', null, this );
this.controller.state().off( 'change:edge change:gutter', this.css, this );
this.model.off( 'change:edge change:gutter', this.css, this );
$(window).off( 'resize.attachments', this._resizeCss );
},
css: function() {
var $css = $( '#' + this.el.id + '-css' ),
state = this.controller.state();
var $css = $( '#' + this.el.id + '-css' );
if ( $css.length )
$css.remove();
media.view.Attachments.$head().append( this.template({
id: this.el.id,
edge: state.get('edge'),
gutter: state.get('gutter')
edge: this.edge(),
gutter: this.model.get('gutter')
}) );
},
edge: function() {
var edge = this.model.get('edge'),
gutter, width, columns;
if ( ! this.$el.is(':visible') )
return edge;
gutter = this.model.get('gutter') * 2;
width = this.$el.width() - gutter;
columns = Math.ceil( width / ( edge + gutter ) );
edge = Math.floor( ( width - ( columns * gutter ) ) / columns );
return edge;
},
initSortable: function() {
var collection = this.collection,
from;
@ -1393,7 +1605,7 @@
render: function() {
// If there are no elements, load some.
if ( ! this.collection.length ) {
this.collection.more();
this.collection.more().done( this.scroll );
this.$el.empty();
return this;
}
@ -1410,6 +1622,7 @@
// Then, trigger the scroll event to check if we're within the
// threshold to query for additional attachments.
this.scroll();
return this;
},
@ -1441,7 +1654,7 @@
return;
if ( this.el.scrollHeight < this.el.scrollTop + ( this.el.clientHeight * this.options.refreshThreshold ) ) {
this.collection.more();
this.collection.more().done( this.scroll );
}
}
}, {
@ -1482,6 +1695,54 @@
}
});
/**
* wp.media.view.AttachmentsBrowser
*/
media.view.AttachmentsBrowser = Backbone.View.extend({
tagName: 'div',
className: 'attachments-browser',
initialize: function() {
this.controller = this.options.controller;
_.defaults( this.options, {
search: true,
upload: false,
total: true
});
this.toolbar = new media.view.Toolbar({
controller: this.controller
});
if ( this.options.search ) {
this.toolbar.add( 'search', new media.view.Search({
controller: this.controller,
model: this.collection.props,
priority: -40
}) );
}
this.attachments = new media.view.Attachments({
controller: this.controller,
collection: this.collection,
model: this.model,
sortable: this.options.sortable,
// The single `Attachment` view to be used in the `Attachments` view.
AttachmentView: media.view.Attachment.Library
});
},
render: function() {
this.toolbar.$el.detach();
this.attachments.$el.detach();
this.$el.html([ this.toolbar.render().el, this.attachments.render().el ]);
return this;
}
});
/**
* wp.media.view.SelectionPreview
*/
@ -1533,6 +1794,83 @@
}
});
/**
* wp.media.view.Selection
*/
media.view.Selection = Backbone.View.extend({
tagName: 'div',
className: 'media-selection',
template: media.template('media-selection'),
events: {
'click .clear-selection': 'clear'
},
initialize: function() {
_.defaults( this.options, {
clearable: true
});
this.controller = this.options.controller;
this.attachments = new media.view.Attachments({
controller: this.controller,
collection: this.collection,
sortable: true,
model: new Backbone.Model({
edge: 40,
gutter: 5
}),
// The single `Attachment` view to be used in the `Attachments` view.
AttachmentView: media.view.Attachment.Selection
});
this.collection.on( 'add remove reset', this.refresh, this );
},
destroy: function() {
this.collection.off( 'add remove reset', this.refresh, this );
},
render: function() {
this.attachments.$el.detach();
this.attachments.render();
this.$el.html( this.template( this.options ) );
this.$('.selection-view').replaceWith( this.attachments.$el );
this.refresh();
return this;
},
refresh: function() {
// If the selection hasn't been rendered, bail.
if ( ! this.$el.children().length )
return;
// If nothing is selected, display nothing.
this.$el.toggleClass( 'empty', ! this.collection.length );
this.$('.count').text( this.collection.length + ' ' + l10n.selected );
},
clear: function( event ) {
event.preventDefault();
this.collection.clear();
}
});
/**
* wp.media.view.Attachment.Selection
*/
media.view.Attachment.Selection = media.view.Attachment.extend({
// On click, just select the model, instead of removing the model from
// the selection.
toggleSelection: function() {
this.controller.state().get('selection').single( this.model );
}
});
/**
* wp.media.view.Settings

View File

@ -1300,7 +1300,9 @@ function wp_print_media_templates( $attachment ) {
<h3 class="media-modal-title"><%- title %></h3>
<a class="media-modal-close" href="" title="<?php esc_attr_e('Close'); ?>">&times;</a>
</div>
<div class="media-modal-backdrop"></div>
<div class="media-modal-backdrop">
<div></div>
</div>
</script>
<script type="text/html" id="tmpl-uploader-window">
@ -1310,15 +1312,11 @@ function wp_print_media_templates( $attachment ) {
</script>
<script type="text/html" id="tmpl-uploader-inline">
<h3><?php _e( 'Drop files here' ); ?></h3>
<!--<span><?php _ex( 'or', 'Uploader: Drop files here - or - Select Files' ); ?></span>-->
<a href="#" class="browser button-secondary"><?php _e( 'Select Files' ); ?></a>
<div class="media-progress-bar"><div></div></div>
</script>
<script type="text/html" id="tmpl-sidebar">
<h2 class="sidebar-title"><%- title %></h2>
<div class="sidebar-content"></div>
<div class="uploader-inline-content">
<h3><?php _e( 'Drop files anywhere to upload' ); ?></h3>
<a href="#" class="browser button button-hero"><?php _e( 'Select Files' ); ?></a>
<div class="media-progress-bar"><div></div></div>
</div>
</script>
<script type="text/html" id="tmpl-attachment">
@ -1360,6 +1358,7 @@ function wp_print_media_templates( $attachment ) {
</script>
<script type="text/html" id="tmpl-attachment-details">
<h3><?php _e('Edit Attachment Details'); ?></h3>
<div class="attachment-preview attachment-details-preview type-<%- type %> subtype-<%- subtype %> <%- orientation %>">
<% if ( uploading ) { %>
<div class="media-progress-bar"><div></div></div>
@ -1392,6 +1391,16 @@ function wp_print_media_templates( $attachment ) {
<% } %>
</script>
<script type="text/html" id="tmpl-media-selection">
<div class="selection-info">
<span class="count"></span>
<% if ( clearable ) { %>
<a class="clear-selection" href="#"><?php _e('Clear'); ?></a>
<% } %>
</div>
<div class="selection-view"></div>
</script>
<script type="text/html" id="tmpl-media-selection-preview">
<div class="selected-img selected-count-<%- count %>">
<% if ( thumbnail ) { %>
@ -1486,7 +1495,7 @@ function wp_print_media_templates( $attachment ) {
<script type="text/html" id="tmpl-attachments-css">
<style type="text/css" id="<%- id %>-css">
#<%- id %> {
padding: <%- gutter %>px;
padding: 0 <%- gutter %>px;
}
#<%- id %> .attachment {

View File

@ -327,12 +327,20 @@ function wp_default_scripts( &$scripts ) {
'search' => __( 'Search' ),
'cancel' => __( 'Cancel' ),
'addImages' => __( 'Add images' ),
'selected' => __( 'selected' ),
// Upload
'uploadFilesTitle' => __( 'Upload Files' ),
'selectFiles' => __( 'Select files' ),
// Library
'mediaLibraryTitle' => __( 'Media Library' ),
'createNewGallery' => __( 'Create a new gallery' ),
'insertIntoPost' => __( 'Insert into post' ),
'addToGallery' => __( 'Add to gallery' ),
'mediaLibraryTitle' => __( 'Media Library' ),
'createNewGallery' => __( 'Create a new gallery' ),
'insertIntoPost' => __( 'Insert into post' ),
'addToGallery' => __( 'Add to gallery' ),
// Embed
'embedFromUrlTitle' => __( 'Embed From URL' ),
// Gallery
'createGalleryTitle' => __( 'Create Gallery' ),