Make WordPress Core

Ticket #21483: 21483.wip.1.diff

File 21483.wip.1.diff, 16.5 KB (added by celloexpressions, 10 years ago)

Rough first pass with the new js-templated-controls framework. Implements previews of all media types, upload and image controls, and basic attempt and reasonable back-compat. Background Image control not yet updated to demonstrate back-compat with sub-classes of WP_Customize_Image_Control. Previewing of existing (saved) control setting values not yet implemented.

  • src/wp-admin/css/customize-controls.css

     
    1010        font-size: 14px;
    1111}
    1212
     13#customize-controls img {
     14        max-width: 100%;
     15}
     16
    1317#customize-controls .submit {
    1418        text-align: center;
    1519}
     
    414418        margin-right: 5px;
    415419}
    416420
     421.customize-control .attachment-thumb.type-icon {
     422        float: left;
     423        margin-right: 12px;
     424}
     425
     426.customize-control .attachment-title {
     427        font-weight: bold;
     428        margin: 0 0 12px 0;
     429}
     430
     431.customize-control .remove-button {
     432        margin-left: 8px;
     433        vertical-align: middle;
     434}
     435
    417436#customize-preview iframe {
    418437        width: 100%;
    419438        height: 100%;
  • src/wp-admin/js/customize-controls.js

     
    200200         * @augments wp.customize.Class
    201201         */
    202202        api.UploadControl = api.Control.extend({
     203
     204                /**
     205                 * Setup control and do event bindings.
     206                 */
    203207                ready: function() {
    204                         var control = this;
     208                       
     209                        // Cache buttons for re-use.
     210                        this.$button = this.container.find( '.upload-button' );
     211                        this.$img = this.container.find( '.thumbnail-image img' );
     212                        this.$removeButton = this.container.find( '.remove-button' );
    205213
    206                         this.params.removed = this.params.removed || '';
     214                        // Shortcut so that we don't have to use _.bind every time we add a callback.
     215                        _.bindAll( this, 'removeFile', 'reRender', 'upload', 'select' );
    207216
    208                         this.success = $.proxy( this.success, this );
     217                        // Ensure clicking "remove" removes the file.
     218                        this.$removeButton.on( 'click', this.removeFile );
    209219
    210                         this.uploader = $.extend({
    211                                 container: this.container,
    212                                 browser:   this.container.find('.upload'),
    213                                 dropzone:  this.container.find('.upload-dropzone'),
    214                                 success:   this.success,
    215                                 plupload:  {},
    216                                 params:    {}
    217                         }, this.uploader || {} );
     220                        // Bind upload button, and image (if it exists).
     221                        this.$button.on( 'click keydown', this.upload );
     222                        this.$img.on( 'click keydown', this.upload );
     223                },
    218224
    219                         if ( control.params.extensions ) {
    220                                 control.uploader.plupload.filters = [{
    221                                         title:      api.l10n.allowedFiles,
    222                                         extensions: control.params.extensions
    223                                 }];
     225                /**
     226                 * Remember that _.bind was used to maintain `this` as the control
     227                 * object rather than the usual jQuery way of binding to the DOM element.
     228                 */
     229                upload: function( event ) {
     230                        event.preventDefault();
     231
     232                        if ( ! this.frame ) {
     233                                this.initFrame();
    224234                        }
    225235
    226                         if ( control.params.context )
    227                                 control.uploader.params['post_data[context]'] = this.params.context;
     236                        this.frame.open();
     237                },
    228238
    229                         if ( api.settings.theme.stylesheet )
    230                                 control.uploader.params['post_data[theme]'] = api.settings.theme.stylesheet;
     239                /**
     240                 * Set the media frame so that it can be reused and accessed when needed.
     241                 */
     242                initFrame: function() {
     243                        this.frame = wp.media({
     244                                // The title of the media modal
     245                                title: this.params.button_labels.frame_title,
    231246
    232                         this.uploader = new wp.Uploader( this.uploader );
     247                                // Restrict to specified mime type.
     248                                // @todo try to map $extensions to this in PHP.
     249                                library: {
     250                                        type: this.params.mime_type
     251                                },
     252                                button: {
     253                                        // Change the submit button label.
     254                                        text: this.params.button_labels.frame_button
     255                                },
     256                                multiple: false
     257                        });
    233258
    234                         this.remover = this.container.find('.remove');
    235                         this.remover.on( 'click keydown', function( event ) {
    236                                 if ( event.type === 'keydown' &&  13 !== event.which ) // enter
    237                                         return;
     259                        // When a file is selected, run a callback.
     260                        this.frame.on( 'select', this.select );
     261                },
    238262
    239                                 control.setting.set( control.params.removed );
    240                                 event.preventDefault();
    241                         });
     263                /**
     264                 * Fired when an image is selected in the media modal. Gets the selected
     265                 * image information, and sets it within the control.
     266                 */
     267                select: function() {
     268                        // Get the attachment from the modal frame.
     269                        var attachment = this.frame.state().get( 'selection' ).first().toJSON();
    242270
    243                         this.removerVisibility = $.proxy( this.removerVisibility, this );
    244                         this.setting.bind( this.removerVisibility );
    245                         this.removerVisibility( this.setting.get() );
     271                        this.params.attachment = attachment;
     272
     273                        // Set the Customizer setting; the callback takes care of rendering.
     274                        this.setting( attachment.url );
     275                        this.reRender();
    246276                },
    247                 success: function( attachment ) {
    248                         this.setting.set( attachment.get('url') );
     277
     278                /**
     279                 * Called on whenever a setting is changed.
     280                 *
     281                 */
     282                reRender: function() {
     283                        // @todo: something else is needed to preview audio/video files
     284                        var self = this;
     285                        self.container.html('');
     286                        self.renderContent( function() {
     287                                // Don't call ready() until the content has rendered.
     288                                self.ready();
     289                        } );
    249290                },
    250                 removerVisibility: function( to ) {
    251                         this.remover.toggle( to != this.params.removed );
    252                 }
     291
     292
     293                /**
     294                 * Called when the "Remove" link is clicked. Empties the setting.
     295                 * @param {object} event jQuery Event object from click event
     296                 */
     297                removeFile: function( event ) {
     298                        event.preventDefault();
     299                        this.setting( '' ); // @todo maybe set to default instead
     300                        this.params.attachment = {};
     301                        this.reRender();
     302                }
    253303        });
    254304
    255305        /**
  • src/wp-includes/class-wp-customize-control.php

     
    580580 * @since 3.4.0
    581581 */
    582582class WP_Customize_Upload_Control extends WP_Customize_Control {
    583         public $type    = 'upload';
    584         public $removed = '';
    585         public $context;
    586         public $extensions = array();
     583        public $type     = 'upload';
     584        public $mime_type = '';
     585        public $button_labels = array(
     586                'select' => 'Select File', // @todo PHP doesn't like letting us translate these
     587                'change' => 'Change File',
     588                'remove' => 'Remove',
     589                'frame_title' => 'Select File',
     590                'frame_button' => 'Choose File',
     591        );
     592        public $removed = ''; // unused
     593        public $context; // unused
     594        public $extensions = array(); // unused
    587595
    588596        /**
    589597         * Enqueue control related scripts/styles.
     
    591599         * @since 3.4.0
    592600         */
    593601        public function enqueue() {
    594                 wp_enqueue_script( 'wp-plupload' );
     602                wp_enqueue_media();
    595603        }
    596604
    597605        /**
     
    602610         */
    603611        public function to_json() {
    604612                parent::to_json();
     613                $this->json['mime_type'] = $this->mime_type;
     614                $this->json['button_labels'] = $this->button_labels;
     615//              $this->json['attachment'] = array();
    605616
    606                 $this->json['removed'] = $this->removed;
     617                // Get the attachment model for the existing file, or make one.
     618/*              if ( $this->setting ) {
     619                        $file_url = $this->setting->default;
     620                        $attachment_id = attachment_url_to_postid( $file_url );
     621                        $this->json['post_id'] = $attachment_id;
     622                }
     623*/      }
    607624
    608                 if ( $this->context )
    609                         $this->json['context'] = $this->context;
    610 
    611                 if ( $this->extensions )
    612                         $this->json['extensions'] = implode( ',', $this->extensions );
    613         }
    614 
    615625        /**
    616          * Render the control's content.
     626         * Don't render any content for this control from PHP.
    617627         *
     628         * @see WP_Customize_Upload_Control::content_template()
    618629         * @since 3.4.0
    619630         */
    620         public function render_content() {
     631        public function render_content() {}
     632
     633        /**
     634         * Render a JS template for the content of the upload control.
     635         *
     636         * @since 4.1.0
     637         */
     638        public function content_template() {
    621639                ?>
    622640                <label>
    623                         <?php if ( ! empty( $this->label ) ) : ?>
    624                                 <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
    625                         <?php endif;
    626                         if ( ! empty( $this->description ) ) : ?>
    627                                 <span class="description customize-control-description"><?php echo $this->description; ?></span>
    628                         <?php endif; ?>
    629                         <div>
    630                                 <a href="#" class="button-secondary upload"><?php _e( 'Upload' ); ?></a>
    631                                 <a href="#" class="remove"><?php _e( 'Remove' ); ?></a>
    632                         </div>
     641                        <# if ( data.label ) { #>
     642                                <span class="customize-control-title">{{ data.label }}</span>
     643                        <# } #>
     644                        <# if ( data.description ) { #>
     645                                <span class="description customize-control-description">{{ data.description }}</span>
     646                        <# } #>
     647
     648                        <# if ( data.attachment && data.attachment.id ) { #>
     649                                <div class="attachment-media-view {{ data.attachment.orientation }}">
     650                                        <div class="thumbnail thumbnail-{{ data.attachment.type }}">
     651                                                <# if ( 'image' === data.attachment.type && data.attachment.sizes && data.attachment.sizes.medium ) { #>
     652                                                        <img class="attachment-thumb" src="{{ data.attachment.sizes.medium.url }}" draggable="false" />
     653                                                <# } else if ( 'image' === data.attachment.type && data.attachment.sizes && data.attachment.sizes.full ) { #>
     654                                                        <img class="attachment-thumb" src="{{ data.attachment.sizes.full.url }}" draggable="false" />
     655                                                <# } else if ( -1 === jQuery.inArray( data.attachment.type, [ 'audio', 'video' ] ) ) { #>
     656                                                        <img class="attachment-thumb type-icon" src="{{ data.attachment.icon }}" class="icon" draggable="false" />
     657                                                        <p class="attachment-title">{{ data.attachment.title }}</p>
     658                                                <# } #>
     659
     660                                                <# if ( 'audio' === data.attachment.type ) { #>
     661                                                <div class="wp-media-wrapper">
     662                                                        <audio style="visibility: hidden" controls class="wp-audio-shortcode" width="100%" preload="none">
     663                                                                <source type="{{ data.attachment.mime }}" src="{{ data.attachment.url }}"/>
     664                                                        </audio>
     665                                                </div>
     666                                                <# } else if ( 'video' === data.attachment.type ) {
     667                                                        var w_rule = h_rule = '';
     668                                                        if ( data.attachment.width ) {
     669                                                                w_rule = 'width: ' + data.attachment.width + 'px;';
     670                                                        } else if ( wp.media.view.settings.contentWidth ) {
     671                                                                w_rule = 'width: ' + wp.media.view.settings.contentWidth + 'px;';
     672                                                        }
     673                                                        if ( data.attachment.height ) {
     674                                                                h_rule = 'height: ' + data.attachment.height + 'px;';
     675                                                        }
     676                                                        #>
     677                                                        <div style="{{ w_rule }}{{ h_rule }}" class="wp-media-wrapper wp-video">
     678                                                                <video controls="controls" class="wp-video-shortcode" preload="metadata"
     679                                                                        <# if ( data.attachment.width ) { #>width="{{ data.attachment.width }}"<# } #>
     680                                                                        <# if ( data.attachment.height ) { #>height="{{ data.attachment.height }}"<# } #>
     681                                                                        <# if ( data.attachment.image && data.attachment.image.src !== data.attachment.icon ) { #>poster="{{ data.attachment.image.src }}"<# } #>>
     682                                                                        <source type="{{ data.attachment.mime }}" src="{{ data.attachment.url }}"/>
     683                                                                </video>
     684                                                        </div>
     685                                                <# } #>
     686                                        </div>
     687                                </div>
     688                                <a class="button upload-button" href="#"><?php echo $this->button_labels['change']; ?></a>
     689                                <a class="remove-button" href="#"><?php echo $this->button_labels['remove']; ?></a>
     690                        <# } else { #>
     691                                <a class="button upload-button" href="#"><?php echo $this->button_labels['select']; ?></a>
     692                        <# } #>
    633693                </label>
    634694                <?php
    635695        }
     
    644704 */
    645705class WP_Customize_Image_Control extends WP_Customize_Upload_Control {
    646706        public $type = 'image';
    647         public $get_url;
    648         public $statuses;
    649         public $extensions = array( 'jpg', 'jpeg', 'gif', 'png' );
     707        public $mime_type = 'image';
     708        public $button_labels = array(
     709                'select' => 'Select Image', // @todo PHP doesn't like letting us translate these
     710                'change' => 'Change Image',
     711                'remove' => 'Remove',
     712                'frame_title' => 'Select Image',
     713                'frame_button' => 'Choose Image',
     714        );
    650715
    651         protected $tabs = array();
    652 
    653716        /**
    654          * Constructor.
    655          *
    656          * @since 3.4.0
    657          * @uses WP_Customize_Upload_Control::__construct()
    658          *
    659          * @param WP_Customize_Manager $manager
    660          * @param string $id
    661          * @param array $args
    662          */
    663         public function __construct( $manager, $id, $args ) {
    664                 $this->statuses = array( '' => __('No Image') );
    665 
    666                 parent::__construct( $manager, $id, $args );
    667 
    668                 $this->add_tab( 'upload-new', __('Upload New'), array( $this, 'tab_upload_new' ) );
    669                 $this->add_tab( 'uploaded',   __('Uploaded'),   array( $this, 'tab_uploaded' ) );
    670 
    671                 // Early priority to occur before $this->manager->prepare_controls();
    672                 add_action( 'customize_controls_init', array( $this, 'prepare_control' ), 5 );
    673         }
    674 
    675         /**
    676          * Prepares the control.
    677          *
    678          * If no tabs exist, removes the control from the manager.
    679          *
    680          * @since 3.4.2
    681          */
    682         public function prepare_control() {
    683                 if ( ! $this->tabs )
    684                         $this->manager->remove_control( $this->id );
    685         }
    686 
    687         /**
    688          * Refresh the parameters passed to the JavaScript via JSON.
    689          *
    690          * @since 3.4.0
    691          * @uses WP_Customize_Upload_Control::to_json()
    692          */
    693         public function to_json() {
    694                 parent::to_json();
    695                 $this->json['statuses'] = $this->statuses;
    696         }
    697 
    698         /**
    699          * Render the control's content.
    700          *
    701          * @since 3.4.0
    702          */
    703         public function render_content() {
    704                 $src = $this->value();
    705                 if ( isset( $this->get_url ) )
    706                         $src = call_user_func( $this->get_url, $src );
    707 
    708                 ?>
    709                 <div class="customize-image-picker">
    710                         <?php if ( ! empty( $this->label ) ) : ?>
    711                                 <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
    712                         <?php endif;
    713                         if ( ! empty( $this->description ) ) : ?>
    714                                 <span class="description customize-control-description"><?php echo $this->description; ?></span>
    715                         <?php endif; ?>
    716 
    717                         <div class="customize-control-content">
    718                                 <div class="dropdown preview-thumbnail" tabindex="0">
    719                                         <div class="dropdown-content">
    720                                                 <?php if ( empty( $src ) ): ?>
    721                                                         <img style="display:none;" />
    722                                                 <?php else: ?>
    723                                                         <img src="<?php echo esc_url( set_url_scheme( $src ) ); ?>" />
    724                                                 <?php endif; ?>
    725                                                 <div class="dropdown-status"></div>
    726                                         </div>
    727                                         <div class="dropdown-arrow"></div>
    728                                 </div>
    729                         </div>
    730 
    731                         <div class="library">
    732                                 <ul>
    733                                         <?php foreach ( $this->tabs as $id => $tab ): ?>
    734                                                 <li data-customize-tab='<?php echo esc_attr( $id ); ?>' tabindex='0'>
    735                                                         <?php echo esc_html( $tab['label'] ); ?>
    736                                                 </li>
    737                                         <?php endforeach; ?>
    738                                 </ul>
    739                                 <?php foreach ( $this->tabs as $id => $tab ): ?>
    740                                         <div class="library-content" data-customize-tab='<?php echo esc_attr( $id ); ?>'>
    741                                                 <?php call_user_func( $tab['callback'] ); ?>
    742                                         </div>
    743                                 <?php endforeach; ?>
    744                         </div>
    745 
    746                         <div class="actions">
    747                                 <a href="#" class="remove"><?php _e( 'Remove Image' ); ?></a>
    748                         </div>
    749                 </div>
    750                 <?php
    751         }
    752 
    753         /**
    754717         * Add a tab to the control.
    755718         *
    756719         * @since 3.4.0
     720         * @deprecated 4.1.0
    757721         *
    758722         * @param string $id
    759723         * @param string $label
    760724         * @param mixed $callback
    761725         */
    762         public function add_tab( $id, $label, $callback ) {
    763                 $this->tabs[ $id ] = array(
    764                         'label'    => $label,
    765                         'callback' => $callback,
    766                 );
    767         }
     726        public function add_tab( $id, $label, $callback ) {}
    768727
    769728        /**
    770729         * Remove a tab from the control.
    771730         *
    772731         * @since 3.4.0
     732         * @deprecated 4.1.0
    773733         *
    774734         * @param string $id
    775735         */
    776         public function remove_tab( $id ) {
    777                 unset( $this->tabs[ $id ] );
    778         }
    779 
    780         /**
    781          * @since 3.4.0
    782          */
    783         public function tab_upload_new() {
    784                 if ( ! _device_can_upload() ) {
    785                         echo '<p>' . sprintf( __('The web browser on your device cannot be used to upload files. You may be able to use the <a href="%s">native app for your device</a> instead.'), 'https://apps.wordpress.org/' ) . '</p>';
    786                 } else {
    787                         ?>
    788                         <div class="upload-dropzone">
    789                                 <?php _e('Drop a file here or <a href="#" class="upload">select a file</a>.'); ?>
    790                         </div>
    791                         <div class="upload-fallback">
    792                                 <span class="button-secondary"><?php _e('Select File'); ?></span>
    793                         </div>
    794                         <?php
    795                 }
    796         }
    797 
    798         /**
    799          * @since 3.4.0
    800          */
    801         public function tab_uploaded() {
    802                 ?>
    803                 <div class="uploaded-target"></div>
    804                 <?php
    805         }
    806 
    807         /**
    808          * @since 3.4.0
    809          *
    810          * @param string $url
    811          * @param string $thumbnail_url
    812          */
    813         public function print_tab_image( $url, $thumbnail_url = null ) {
    814                 $url = set_url_scheme( $url );
    815                 $thumbnail_url = ( $thumbnail_url ) ? set_url_scheme( $thumbnail_url ) : $url;
    816                 ?>
    817                 <a href="#" class="thumbnail" data-customize-image-value="<?php echo esc_url( $url ); ?>">
    818                         <img src="<?php echo esc_url( $thumbnail_url ); ?>" />
    819                 </a>
    820                 <?php
    821         }
     736        public function remove_tab( $id ) {}
    822737}
    823738
    824739/**
  • src/wp-includes/class-wp-customize-manager.php

     
    962962
    963963                /* Control Types (custom control classes) */
    964964                $this->register_control_type( 'WP_Customize_Color_Control' );
     965                $this->register_control_type( 'WP_Customize_Upload_Control' );
     966                $this->register_control_type( 'WP_Customize_Image_Control' );
    965967
    966968                /* Site Title & Tagline */
    967969