Index: src/wp-admin/css/customize-controls.css =================================================================== --- src/wp-admin/css/customize-controls.css (revision 29719) +++ src/wp-admin/css/customize-controls.css (working copy) @@ -10,6 +10,10 @@ font-size: 14px; } +#customize-controls img { + max-width: 100%; +} + #customize-controls .submit { text-align: center; } @@ -414,6 +418,25 @@ margin-right: 5px; } +.customize-control-upload .remove-file, +.customize-control-image .remove-file, +.customize-control-background_image .remove-file { + margin-left: 15px; + vertical-align: middle; +} + +.customize-control-upload .file-preview, +.customize-control-image .file-preview, +.customize-control-background_image .file-preview { + margin-bottom: 6px; +} + +.customize-control-upload .action-container, +.customize-control-image .action-container, +.customize-control-background_image .action-container { + margin-bottom: 12px; +} + #customize-preview iframe { width: 100%; height: 100%; Index: src/wp-admin/js/customize-controls.js =================================================================== --- src/wp-admin/js/customize-controls.js (revision 29719) +++ src/wp-admin/js/customize-controls.js (working copy) @@ -179,55 +179,120 @@ * @augments wp.customize.Class */ api.UploadControl = api.Control.extend({ + + /** + * Setup control and do event bindings. + */ ready: function() { - var control = this; + // this.container is a jQuery object of the container element. - this.params.removed = this.params.removed || ''; + // Set up preview, preview container, and button elements. Cache for re-use. + this.$previewContainer = $( '
' ).appendTo( this.container ); + this.$preview = $( '' ).prependTo( this.$previewContainer ); // @todo actual previews, and a better way to override in subclasses. + this.$actionContainer = $( '' ).appendTo( this.container ); + this.$button = $( '' + 'Select File' + '' ).appendTo( this.$actionContainer ); // @todo i18n + this.$removeButton = $( '' + 'Remove' + '' ).appendTo( this.$actionContainer ); // @todo i18n - this.success = $.proxy( this.success, this ); + // Shortcut so that we don't have to use _.bind every time we add a callback. + _.bindAll( this, 'removeFile', 'upload', 'render', 'select' ); - this.uploader = $.extend({ - container: this.container, - browser: this.container.find('.upload'), - dropzone: this.container.find('.upload-dropzone'), - success: this.success, - plupload: {}, - params: {} - }, this.uploader || {} ); + // Ensure clicking "remove image" removes the image. + this.$removeButton.on( 'click', this.removeFile ); - if ( control.params.extensions ) { - control.uploader.plupload.filters = [{ - title: api.l10n.allowedFiles, - extensions: control.params.extensions - }]; + // Bind upload button + this.$button.on( 'click', this.upload ); + + // Call render method whenever setting is changed. + this.setting.bind( 'change', this.render ); + + // Do initial rendering. + this.render(); + }, + + /** + * Remember that _.bind was used to maintain `this` as the control + * object rather than the usual jQuery way of binding to the DOM element. + */ + upload: function( event ) { + event.preventDefault(); + + if ( ! this.frame ) { + this.initFrame(); } - if ( control.params.context ) - control.uploader.params['post_data[context]'] = this.params.context; + this.frame.open(); + }, - if ( api.settings.theme.stylesheet ) - control.uploader.params['post_data[theme]'] = api.settings.theme.stylesheet; + /** + * Set the media frame so that it can be reused and accessed when needed. + */ + initFrame: function() { + this.frame = wp.media({ + // The title of the media modal + title: 'Select File', // @todo i18n - this.uploader = new wp.Uploader( this.uploader ); + // Restrict to specified mime type. + // @todo try to map $extensions to this in PHP. + library: { + type: this.params.mime_type + }, - this.remover = this.container.find('.remove'); - this.remover.on( 'click keydown', function( event ) { - if ( event.type === 'keydown' && 13 !== event.which ) // enter - return; + button: { + // Change the submit button label. + text: 'Choose File' + }, - control.setting.set( control.params.removed ); - event.preventDefault(); + multiple: false }); - this.removerVisibility = $.proxy( this.removerVisibility, this ); - this.setting.bind( this.removerVisibility ); - this.removerVisibility( this.setting.get() ); + // When a file is selected, run a callback. + this.frame.on( 'select', this.select ); }, - success: function( attachment ) { - this.setting.set( attachment.get('url') ); + + /** + * Fired when an image is selected in the media modal. Gets the selected + * image information, and sets it within the control. + */ + select: function() { + // Get the attachment from the modal frame. + var attachment = this.frame.state().get( 'selection' ).first().toJSON(); + + // Set the Customizer setting; the callback takes care of rendering. + this.setting( attachment.url ); }, - removerVisibility: function( to ) { - this.remover.toggle( to != this.params.removed ); + + /** + * Called on init and whenever a setting is changed. + * + * @todo figure out the best way to render by type. + */ + render: function() { + var value = this.setting(); + if ( value ) { + // Show a preview of some sort (just the plaintext URL for now). + this.$preview.text( value ); + this.$previewContainer.show(); + this.$button.text( 'Change File' ); + this.$removeButton.show(); + } else { + this.$button.text( 'Select File' ); + this.$removeButton.hide(); + this.$previewContainer.hide(); + } + }, + + // @todo handle the previewing part here instead of render, to facilitate subclasses? + preview: function() { + + }, + + /** + * Called when the "Remove" link is clicked. Empties the setting. + * @param {object} event jQuery Event object from click event + */ + removeFile: function( event ) { + event.preventDefault(); + this.setting( '' ); } }); @@ -238,120 +303,53 @@ * @augments wp.customize.Class */ api.ImageControl = api.UploadControl.extend({ - ready: function() { - var control = this, - panels; + + /** + * Set the media frame so that it can be reused and accessed when needed. + */ + initFrame: function() { + this.frame = wp.media({ + // The title of the media modal + title: 'Select Image', // @todo i18n - this.uploader = { - init: function() { - var fallback, button; + // Restrict to specified mime type. + // @todo try to map $extensions to this in PHP. + library: { + type: this.params.mime_type + }, - if ( this.supports.dragdrop ) - return; + button: { + // Change the submit button label. + text: 'Choose Image' + }, - // Maintain references while wrapping the fallback button. - fallback = control.container.find( '.upload-fallback' ); - button = fallback.children().detach(); - - this.browser.detach().empty().append( button ); - fallback.append( this.browser ).show(); - } - }; - - api.UploadControl.prototype.ready.call( this ); - - this.thumbnail = this.container.find('.preview-thumbnail img'); - this.thumbnailSrc = $.proxy( this.thumbnailSrc, this ); - this.setting.bind( this.thumbnailSrc ); - - this.library = this.container.find('.library'); - - // Generate tab objects - this.tabs = {}; - panels = this.library.find('.library-content'); - - this.library.children('ul').children('li').each( function() { - var link = $(this), - id = link.data('customizeTab'), - panel = panels.filter('[data-customize-tab="' + id + '"]'); - - control.tabs[ id ] = { - both: link.add( panel ), - link: link, - panel: panel - }; + multiple: false }); - // Bind tab switch events - this.library.children('ul').on( 'click keydown', 'li', function( event ) { - if ( event.type === 'keydown' && 13 !== event.which ) // enter - return; + // When a file is selected, run a callback. + this.frame.on( 'select', this.select ); + }, - var id = $(this).data('customizeTab'), - tab = control.tabs[ id ]; - - event.preventDefault(); - - if ( tab.link.hasClass('library-selected') ) - return; - - control.selected.both.removeClass('library-selected'); - control.selected = tab; - control.selected.both.addClass('library-selected'); - }); - - // Bind events to switch image urls. - this.library.on( 'click keydown', 'a', function( event ) { - if ( event.type === 'keydown' && 13 !== event.which ) // enter - return; - - var value = $(this).data('customizeImageValue'); - - if ( value ) { - control.setting.set( value ); - event.preventDefault(); + render: function() { + var value = this.setting(); + if ( value ) { + // Maybe setup preview. + if ( ! this.$img ) { + this.$previewContainer.html( '' ); + this.$img = $( '' ).appendTo( this.$previewContainer ); } - }); - if ( this.tabs.uploaded ) { - this.tabs.uploaded.target = this.library.find('.uploaded-target'); - if ( ! this.tabs.uploaded.panel.find('.thumbnail').length ) - this.tabs.uploaded.both.addClass('hidden'); + // Show a preview thumbnail. + this.$img.attr( 'src', value ); + this.$previewContainer.show(); + this.$button.text( 'Change Image' ); + this.$removeButton.show(); + } else { + // Hide thumbnail, show button + this.$button.text( 'Select Image' ); + this.$removeButton.hide(); + this.$previewContainer.hide(); } - - // Select a tab - panels.each( function() { - var tab = control.tabs[ $(this).data('customizeTab') ]; - - // Select the first visible tab. - if ( ! tab.link.hasClass('hidden') ) { - control.selected = tab; - tab.both.addClass('library-selected'); - return false; - } - }); - - this.dropdownInit(); - }, - success: function( attachment ) { - api.UploadControl.prototype.success.call( this, attachment ); - - // Add the uploaded image to the uploaded tab. - if ( this.tabs.uploaded && this.tabs.uploaded.target.length ) { - this.tabs.uploaded.both.removeClass('hidden'); - - // @todo: Do NOT store this on the attachment model. That is bad. - attachment.element = $( '' ) - .data( 'customizeImageValue', attachment.get('url') ) - .append( '' ) - .appendTo( this.tabs.uploaded.target ); - } - }, - thumbnailSrc: function( to ) { - if ( /^(https?:)?\/\//.test( to ) ) - this.thumbnail.prop( 'src', to ).show(); - else - this.thumbnail.hide(); } }); Index: src/wp-includes/class-wp-customize-control.php =================================================================== --- src/wp-includes/class-wp-customize-control.php (revision 29719) +++ src/wp-includes/class-wp-customize-control.php (working copy) @@ -541,9 +541,10 @@ */ class WP_Customize_Upload_Control extends WP_Customize_Control { public $type = 'upload'; - public $removed = ''; - public $context; - public $extensions = array(); + public $mime_type = ''; + public $removed = ''; // unused + public $context; // unused + public $extensions = array(); // unused @todo auto-convert to mime_type and/or enforce in media library. /** * Enqueue control related scripts/styles. @@ -551,7 +552,7 @@ * @since 3.4.0 */ public function enqueue() { - wp_enqueue_script( 'wp-plupload' ); + wp_enqueue_media(); } /** @@ -563,13 +564,7 @@ public function to_json() { parent::to_json(); - $this->json['removed'] = $this->removed; - - if ( $this->context ) - $this->json['context'] = $this->context; - - if ( $this->extensions ) - $this->json['extensions'] = implode( ',', $this->extensions ); + $this->json['mime_type'] = $this->mime_type; } /** @@ -579,18 +574,13 @@ */ public function render_content() { ?> - + label ) ) : ?> + label ); ?> + description ) ) : ?> + description; ?> + + statuses = array( '' => __('No Image') ); - - parent::__construct( $manager, $id, $args ); - - $this->add_tab( 'upload-new', __('Upload New'), array( $this, 'tab_upload_new' ) ); - $this->add_tab( 'uploaded', __('Uploaded'), array( $this, 'tab_uploaded' ) ); - - // Early priority to occur before $this->manager->prepare_controls(); - add_action( 'customize_controls_init', array( $this, 'prepare_control' ), 5 ); - } - - /** - * Prepares the control. - * - * If no tabs exist, removes the control from the manager. - * - * @since 3.4.2 - */ - public function prepare_control() { - if ( ! $this->tabs ) - $this->manager->remove_control( $this->id ); - } - - /** - * Refresh the parameters passed to the JavaScript via JSON. - * - * @since 3.4.0 - * @uses WP_Customize_Upload_Control::to_json() - */ - public function to_json() { - parent::to_json(); - $this->json['statuses'] = $this->statuses; - } - - /** - * Render the control's content. - * - * @since 3.4.0 - */ - public function render_content() { - $src = $this->value(); - if ( isset( $this->get_url ) ) - $src = call_user_func( $this->get_url, $src ); - - ?> -' . sprintf( __('The web browser on your device cannot be used to upload files. You may be able to use the native app for your device instead.'), 'http://apps.wordpress.org/' ) . '
'; - } else { - ?> -