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; 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 { .button:active {
outline: none; outline: none;
} }
@ -294,7 +302,7 @@ input[type="submit"]::-moz-focus-inner {
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 0; left: 0;
margin-top: 5px; margin: 5px 0;
padding: 0.8em 1em; padding: 0.8em 1em;
border-radius: 3px; border-radius: 3px;
@ -314,3 +322,8 @@ input[type="submit"]::-moz-focus-inner {
left: auto; left: auto;
right: 0; right: 0;
} }
.dropdown-flip-y .dropdown {
top: auto;
bottom: 100%;
}

View File

@ -3,10 +3,10 @@
*/ */
.media-modal { .media-modal {
position: fixed; position: fixed;
top: 80px; top: 60px;
left: 60px; left: 40px;
right: 60px; right: 40px;
bottom: 60px; bottom: 40px;
z-index: 125000; z-index: 125000;
} }
@ -21,6 +21,15 @@
z-index: 120000; 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-title,
.media-modal-close { .media-modal-close {
position: absolute; position: absolute;
@ -38,7 +47,7 @@
float: left; float: left;
padding: 0; padding: 0;
margin: 0; margin: 0;
font-size: 1.4em; font-size: 16px;
} }
.media-modal-close { .media-modal-close {
@ -74,12 +83,23 @@
.media-toolbar { .media-toolbar {
position: absolute; position: absolute;
top: 0; top: 0;
left: 220px; left: 0;
right: 0; right: 0;
z-index: 100; z-index: 100;
height: 50px; height: 60px;
padding: 0 10px; padding: 0 16px;
border-bottom: 1px solid #dfdfdf; 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 { .media-toolbar-primary {
@ -94,14 +114,14 @@
.media-toolbar-primary > .media-button-group { .media-toolbar-primary > .media-button-group {
margin-left: 10px; margin-left: 10px;
float: left; float: left;
margin-top: 10px; margin-top: 15px;
} }
.media-toolbar-secondary > .media-button, .media-toolbar-secondary > .media-button,
.media-toolbar-secondary > .media-button-group { .media-toolbar-secondary > .media-button-group {
margin-right: 10px; margin-right: 10px;
float: left; float: left;
margin-top: 10px; margin-top: 15px;
} }
/** /**
@ -110,16 +130,21 @@
.media-sidebar { .media-sidebar {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; right: 0;
bottom: 0; bottom: 61px;
width: 219px; width: 247px;
z-index: 50; padding: 0 16px;
z-index: 75;
background: #f5f5f5; background: #f5f5f5;
border-right: 1px solid #dfdfdf; border-left: 1px solid #dfdfdf;
} }
.hide-sidebar .media-sidebar { .hide-sidebar .media-sidebar {
display: none; right: -280px;
}
.hide-toolbar .media-sidebar {
bottom: 0;
} }
.media-sidebar .sidebar-title { .media-sidebar .sidebar-title {
@ -146,32 +171,66 @@
padding-top: 5px; 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 * Frame
*/ */
.media-frame {
.media-frame .media-content, overflow: hidden;
.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 .media-content { .media-frame .media-content {
position: absolute; position: absolute;
top: 51px; top: 0;
left: 220px; left: 200px;
right: 0; right: 280px;
bottom: 0; bottom: 61px;
height: auto; height: auto;
width: auto; width: auto;
margin: 0; margin: 0;
@ -179,7 +238,11 @@
} }
.media-frame.hide-sidebar .media-content { .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 { .media-frame .media-toolbar .add-to-gallery {
@ -198,6 +261,14 @@
font-family: sans-serif; font-family: sans-serif;
} }
/**
* Attachments
*/
.attachments {
margin: 0;
padding-right: 16px;
}
/** /**
* Attachment * Attachment
*/ */
@ -388,6 +459,28 @@
border-radius: 0; 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 * Progress Bar
*/ */
@ -445,10 +538,10 @@
.uploader-window-content { .uploader-window-content {
position: absolute; position: absolute;
top: 30px; top: 10px;
left: 30px; left: 10px;
right: 30px; right: 10px;
bottom: 30px; bottom: 10px;
border: 1px dashed #fff; border: 1px dashed #fff;
} }
@ -463,7 +556,7 @@
-o-transform: translateY( -50% ); -o-transform: translateY( -50% );
transform: translateY( -50% ); transform: translateY( -50% );
font-size: 18px; font-size: 20px;
font-weight: 200; font-weight: 200;
color: #fff; color: #fff;
padding: 0; padding: 0;
@ -485,8 +578,24 @@
display: block; display: block;
} }
.uploader-inline { .media-content.uploader-inline {
display: none; 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 { .uploader-inline .media-progress-bar {
@ -497,23 +606,85 @@
display: block; display: block;
} }
.media-sidebar .uploader-inline { .uploader-inline .browser {
display: block; display: inline-block !important;
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 100px;
margin: 10px;
padding-top: 10px;
text-align: center;
border: 1px dashed #aaa;
} }
.media-sidebar .uploader-inline h3 { /**
font-weight: 200; * Selection
font-size: 16px; */
margin: 10px 0;
.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
*/ */
.attachment-details {
padding-top: 20px;
}
.attachment-details-preview { .attachment-details-preview {
cursor: default; cursor: default;
} }

View File

@ -83,15 +83,21 @@
state: function( id ) { state: function( id ) {
var previous; var previous;
if ( id ) { if ( ! id )
if ( previous = this.state() ) return this._state ? this.get( this._state ) : null;
previous.trigger('deactivate');
this._state = id;
return this.state().trigger('activate');
}
if ( this._state ) previous = this.state();
return this.get( 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 // wp.media.controller.Library
// --------------------------- // ---------------------------
media.controller.Library = Backbone.Model.extend({ media.controller.Library = media.controller.State.extend({
defaults: { defaults: {
id: 'library', id: 'library',
multiple: false, multiple: false,
@ -133,15 +180,10 @@
if ( ! this.get('gutter') ) if ( ! this.get('gutter') )
this.set( 'gutter', 8 ); this.set( 'gutter', 8 );
this.on( 'activate', this.activate, this ); media.controller.State.prototype.initialize.apply( this, arguments );
this.on( 'deactivate', this.deactivate, this );
}, },
activate: function() { activate: function() {
this.toolbar();
this.sidebar();
this.content();
// If we're in a workflow that supports multiple attachments, // If we're in a workflow that supports multiple attachments,
// automatically select any uploading attachments. // automatically select any uploading attachments.
if ( this.get('multiple') ) if ( this.get('multiple') )
@ -153,6 +195,7 @@
deactivate: function() { deactivate: function() {
var toolbar = this._postLibraryToolbar; var toolbar = this._postLibraryToolbar;
if ( toolbar ) if ( toolbar )
this.get('selection').off( 'add remove', toolbar.visibility, toolbar ); this.get('selection').off( 'add remove', toolbar.visibility, toolbar );
@ -184,30 +227,16 @@
}) ); }) );
this.details(); 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() { content: function() {
var frame = this.frame; var frame = this.frame;
// Content. // Content.
frame.content( new media.view.Attachments({ frame.content( new media.view.AttachmentsBrowser({
controller: frame, controller: frame,
collection: this.get('library'), collection: this.get('library'),
// The single `Attachment` view to be used in the `Attachments` view. model: this
AttachmentView: media.view.Attachment.Library
}).render() ); }).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 // wp.media.controller.Gallery
// --------------------------- // ---------------------------
media.controller.Gallery = media.controller.Library.extend({ media.controller.Gallery = media.controller.Library.extend({
@ -296,6 +384,7 @@
this.frame.content( new media.view.Attachments({ this.frame.content( new media.view.Attachments({
controller: this.frame, controller: this.frame,
collection: this.get('library'), collection: this.get('library'),
model: this,
sortable: true, sortable: true,
// The single `Attachment` view to be used in the `Attachments` view. // The single `Attachment` view to be used in the `Attachments` view.
AttachmentView: media.view.Attachment.Gallery AttachmentView: media.view.Attachment.Gallery
@ -345,7 +434,7 @@
initialize: function() { initialize: function() {
_.defaults( this.options, { _.defaults( this.options, {
state: 'library', state: 'upload',
title: '', title: '',
selection: [], selection: [],
library: {}, library: {},
@ -361,12 +450,12 @@
}, },
render: function() { 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 ) if ( this.modal )
this.modal.render(); 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.children().filter( els ).detach();
this.$el.empty().append( els ); this.$el.empty().append( els );
@ -389,7 +478,12 @@
}, },
createStates: function() { createStates: function() {
var options = this.options; var options = this.options,
menus = {
landing: new media.view.Menu.Landing({
controller: this
})
};
// Create the default `states` collection. // Create the default `states` collection.
this.states = new Backbone.Collection(); this.states = new Backbone.Collection();
@ -404,7 +498,12 @@
new media.controller.Library({ new media.controller.Library({
selection: options.selection, selection: options.selection,
library: media.query( options.library ), 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({ new media.controller.Gallery({
library: options.selection, library: options.selection,
@ -419,7 +518,7 @@
createSubviews: function() { createSubviews: function() {
// Initialize a stub view for each subview region. // 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({ this[ '_' + subview ] = new Backbone.View({
tagName: 'div', tagName: 'div',
className: 'media-' + subview className: 'media-' + subview
@ -450,15 +549,21 @@
_.extend( media.view.Frame.prototype, media.controller.StateMachine.prototype ); _.extend( media.view.Frame.prototype, media.controller.StateMachine.prototype );
// Create methods to fetch and replace individual subviews. // 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 ) { media.view.Frame.prototype[ subview ] = function( view ) {
var previous = this[ '_' + subview ]; var previous = this[ '_' + subview ];
if ( ! view ) if ( ! view )
return previous; return previous;
if ( view === previous )
return;
view.$el.addClass( 'media-' + subview ); view.$el.addClass( 'media-' + subview );
// Remove the hide class.
this.$el.removeClass( 'hide-' + subview );
if ( previous.destroy ) if ( previous.destroy )
previous.destroy(); previous.destroy();
previous.undelegateEvents(); previous.undelegateEvents();
@ -564,17 +669,12 @@
var uploader; var uploader;
this.controller = this.options.controller; 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 || {}, { uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
container: this.inline.$el,
dropzone: this.$el, dropzone: this.$el,
browser: this.inline.$('.browser'), browser: this.$browser,
params: {} params: {}
}); });
@ -647,6 +747,9 @@
initialize: function() { initialize: function() {
this.controller = this.options.controller; this.controller = this.options.controller;
if ( ! this.options.$browser )
this.options.$browser = this.controller.uploader.$browser;
// Track uploading attachments. // Track uploading attachments.
wp.Uploader.queue.on( 'add remove reset change:percent', this.renderUploadProgress, this ); wp.Uploader.queue.on( 'add remove reset change:percent', this.renderUploadProgress, this );
}, },
@ -656,8 +759,17 @@
}, },
render: function() { render: function() {
var $browser = this.options.$browser,
$placeholder;
this.renderUploadProgress(); this.renderUploadProgress();
this.$el.html( this.template( this.options ) ); 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'); this.$bar = this.$('.media-progress-bar div');
return this; return this;
}, },
@ -679,7 +791,6 @@
} }
}); });
/** /**
* wp.media.view.Toolbar * wp.media.view.Toolbar
*/ */
@ -765,6 +876,12 @@
controller = this.options.controller; controller = this.options.controller;
this.options.items = { this.options.items = {
selection: new media.view.Selection({
controller: controller,
collection: selection,
priority: -40
}).render(),
'create-new-gallery': { 'create-new-gallery': {
style: 'primary', style: 'primary',
text: l10n.createNewGallery, text: l10n.createNewGallery,
@ -777,7 +894,7 @@
'insert-into-post': new media.view.ButtonGroup({ 'insert-into-post': new media.view.ButtonGroup({
priority: 30, priority: 30,
classes: 'dropdown-flip-x', classes: 'dropdown-flip-x dropdown-flip-y',
buttons: [ buttons: [
{ {
text: l10n.insertIntoPost, 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', tagName: 'div',
className: 'media-sidebar',
template: media.template('sidebar'),
initialize: function() { initialize: function() {
this.controller = this.options.controller; this.controller = this.options.controller;
this._views = {}; this._views = {};
if ( this.options.views ) this.add( _.extend( {}, this.views, this.options.views ), { silent: true });
this.add( this.options.views, { silent: true }); delete this.views;
delete this.options.views;
if ( ! this.options.silent ) if ( ! this.options.silent )
this.render(); this.render();
@ -1060,18 +1177,7 @@
// Otherwise, `jQuery.html()` will unbind their events. // Otherwise, `jQuery.html()` will unbind their events.
$( els ).detach(); $( els ).detach();
this.$el.html( this.template({ this.$el.html( els );
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();
}
return this; return this;
}, },
@ -1089,6 +1195,9 @@
return this; return this;
} }
if ( ! (view instanceof Backbone.View) )
view = this.toView( view, id, options );
view.controller = view.controller || this.controller; view.controller = view.controller || this.controller;
this._views[ id ] = view; this._views[ id ] = view;
@ -1106,9 +1215,94 @@
if ( ! options || ! options.silent ) if ( ! options || ! options.silent )
this.render(); this.render();
return this; 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 * wp.media.view.Attachment
*/ */
@ -1118,7 +1312,7 @@
template: media.template('attachment'), template: media.template('attachment'),
events: { events: {
'click .attachment-preview': 'toggleSelection', 'mousedown .attachment-preview': 'toggleSelection',
'change .describe': 'describe' 'change .describe': 'describe'
}, },
@ -1322,29 +1516,47 @@
this.initSortable(); 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(); this.css();
}, },
destroy: function() { destroy: function() {
this.collection.off( 'add remove reset', null, this ); 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() { css: function() {
var $css = $( '#' + this.el.id + '-css' ), var $css = $( '#' + this.el.id + '-css' );
state = this.controller.state();
if ( $css.length ) if ( $css.length )
$css.remove(); $css.remove();
media.view.Attachments.$head().append( this.template({ media.view.Attachments.$head().append( this.template({
id: this.el.id, id: this.el.id,
edge: state.get('edge'), edge: this.edge(),
gutter: state.get('gutter') 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() { initSortable: function() {
var collection = this.collection, var collection = this.collection,
from; from;
@ -1393,7 +1605,7 @@
render: function() { render: function() {
// If there are no elements, load some. // If there are no elements, load some.
if ( ! this.collection.length ) { if ( ! this.collection.length ) {
this.collection.more(); this.collection.more().done( this.scroll );
this.$el.empty(); this.$el.empty();
return this; return this;
} }
@ -1410,6 +1622,7 @@
// Then, trigger the scroll event to check if we're within the // Then, trigger the scroll event to check if we're within the
// threshold to query for additional attachments. // threshold to query for additional attachments.
this.scroll(); this.scroll();
return this; return this;
}, },
@ -1441,7 +1654,7 @@
return; return;
if ( this.el.scrollHeight < this.el.scrollTop + ( this.el.clientHeight * this.options.refreshThreshold ) ) { 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 * 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 * wp.media.view.Settings

View File

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

View File

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