Skip to:
Content

BuddyPress.org

Changeset 13179


Ignore:
Timestamp:
12/11/2021 06:52:01 PM (2 years ago)
Author:
imath
Message:

Add a new "Recycle" tab to the Avatar UI

Thanks to this tab users can manage their avatars history. They can choose to:

  • reuse a previously uploaded avatar as their current profile photo,
  • permanently delete an avatar from their avatars history.

Props vapvarun, oztaser

See #8581

Location:
trunk/src
Files:
2 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-core/bp-core-attachments.php

    r13174 r13179  
    410410    // It's an avatar, we need to crop it.
    411411    if ( 'avatar' === $type ) {
    412         $created = bp_core_avatar_handle_crop( array(
    413             'object'        => $r['object'],
    414             'avatar_dir'    => trim( dirname( $attachment_data['subdir'] ), '/' ),
    415             'item_id'       => (int) $r['item_id'],
    416             'original_file' => trailingslashit( $attachment_data['subdir'] ) . $image_file_name,
    417             'crop_w'        => $r['crop_w'],
    418             'crop_h'        => $r['crop_h'],
    419             'crop_x'        => $r['crop_x'],
    420             'crop_y'        => $r['crop_y']
    421         ) );
     412        $created = bp_core_avatar_handle_crop(
     413            array(
     414                'object'        => $r['object'],
     415                'avatar_dir'    => trim( dirname( $attachment_data['subdir'] ), '/' ),
     416                'item_id'       => (int) $r['item_id'],
     417                'original_file' => trailingslashit( $attachment_data['subdir'] ) . $image_file_name,
     418                'crop_w'        => $r['crop_w'],
     419                'crop_h'        => $r['crop_h'],
     420                'crop_x'        => $r['crop_x'],
     421                'crop_y'        => $r['crop_y']
     422            )
     423        );
    422424
    423425    // It's a cover image we need to fit it to feature's dimensions.
    424426    } elseif ( 'cover_image' === $type ) {
    425         $cover_image = bp_attachments_cover_image_generate_file( array(
    426             'file'            => $image_file_path,
    427             'component'       => $r['component'],
    428             'cover_image_dir' => $attachment_data['path']
    429         ) );
     427        $cover_image = bp_attachments_cover_image_generate_file(
     428            array(
     429                'file'            => $image_file_path,
     430                'component'       => $r['component'],
     431                'cover_image_dir' => $attachment_data['path']
     432            )
     433        );
    430434
    431435        $created = ! empty( $cover_image['cover_file'] );
     
    788792        $defaults['multi_selection'] = false;
    789793
    790         // Does the object already has an avatar set.
     794        // Does the object already has an avatar set?
    791795        $has_avatar = $defaults['multipart_params']['bp_params']['has_avatar'];
    792796
    793         // What is the object the avatar belongs to.
     797        // What is the object the avatar belongs to?
    794798        $object = $defaults['multipart_params']['bp_params']['object'];
     799
     800        // Get The item id.
     801        $item_id = $defaults['multipart_params']['bp_params']['item_id'];
    795802
    796803        // Init the Avatar nav.
    797804        $avatar_nav = array(
    798             'upload' => array( 'id' => 'upload', 'caption' => __( 'Upload', 'buddypress' ), 'order' => 0  ),
    799 
    800             // The delete view will only show if the object has an avatar.
    801             'delete' => array( 'id' => 'delete', 'caption' => __( 'Delete', 'buddypress' ), 'order' => 100, 'hide' => (int) ! $has_avatar ),
     805            'upload'  => array(
     806                'id'      => 'upload',
     807                'caption' => __( 'Upload', 'buddypress' ),
     808                'order'   => 0
     809            ),
     810            'delete'  => array(
     811                'id'      => 'delete',
     812                'caption' => __( 'Delete', 'buddypress' ),
     813                'order'   => 100,
     814                'hide'    => (int) ! $has_avatar
     815            ),
    802816        );
     817
     818        if ( 'user' === $object ) {
     819            // Look inside history to see if the user previously uploaded avatars.
     820            $avatars_history = bp_avatar_get_avatars_history( $item_id, $object );
     821
     822            if ( $avatars_history ) {
     823                ksort( $avatars_history );
     824                $settings['history']       = array_values( $avatars_history );
     825                $settings['historyNonces'] = array(
     826                    'recylePrevious' => wp_create_nonce( 'bp_avatar_recycle_previous' ),
     827                    'deletePrevious' => wp_create_nonce( 'bp_avatar_delete_previous' ),
     828                );
     829
     830                $avatar_nav['recycle'] = array(
     831                    'id'      => 'recycle',
     832                    'caption' => __( 'Recycle', 'buddypress' ),
     833                    'order'   => 20,
     834                    'hide'    => (int) empty( $avatars_history ),
     835                );
     836            }
     837        }
    803838
    804839        // Create the Camera Nav if the WebCam capture feature is enabled.
    805840        if ( bp_avatar_use_webcam() && 'user' === $object ) {
    806             $avatar_nav['camera'] = array( 'id' => 'camera', 'caption' => __( 'Take Photo', 'buddypress' ), 'order' => 10 );
     841            $avatar_nav['camera'] = array(
     842                'id'      => 'camera',
     843                'caption' => __( 'Take Photo', 'buddypress' ),
     844                'order'   => 10
     845            );
    807846
    808847            // Set warning messages.
  • trunk/src/bp-core/css/avatar-rtl.css

    r12136 r13179  
    398398}
    399399
     400.avatars-history {
     401    display: flex;
     402    flex-direction: row;
     403}
     404
     405.avatar-history-list {
     406    order: 1;
     407    width: 80%;
     408    max-height: 250px;
     409    overflow-y: auto;
     410}
     411
     412.avatar-history-list label {
     413    display: block;
     414    margin: 0 auto;
     415    width: 60px;
     416}
     417
     418.avatar-history-table td .avatar {
     419    vertical-align: middle;
     420}
     421
     422.avatar-history-table td .avatar.selected {
     423    border: solid 4px #065288;
     424}
     425
     426.avatar-history-actions {
     427    order: 2;
     428    margin-right: 1em;
     429    text-align: center;
     430}
     431
     432.avatar-history-actions p.warning {
     433    text-align: right;
     434}
     435
     436.avatar-history-actions button.delete:not(.disabled):hover {
     437    background-color: #800;
     438    color: #fff;
     439}
     440
     441.avatar-history-actions button.delete:not(.disabled) {
     442    background-color: #fdc;
     443    border: 1px solid #a00;
     444    color: #800;
     445}
     446
    400447@media screen and (min-width: 810px) {
    401448
  • trunk/src/bp-core/css/avatar.css

    r12136 r13179  
    398398}
    399399
     400.avatars-history {
     401    display: flex;
     402    flex-direction: row;
     403}
     404
     405.avatar-history-list {
     406    order: 1;
     407    width: 80%;
     408    max-height: 250px;
     409    overflow-y: auto;
     410}
     411
     412.avatar-history-list label {
     413    display: block;
     414    margin: 0 auto;
     415    width: 60px;
     416}
     417
     418.avatar-history-table td .avatar {
     419    vertical-align: middle;
     420}
     421
     422.avatar-history-table td .avatar.selected {
     423    border: solid 4px #065288;
     424}
     425
     426.avatar-history-actions {
     427    order: 2;
     428    margin-left: 1em;
     429    text-align: center;
     430}
     431
     432.avatar-history-actions p.warning {
     433    text-align: left;
     434}
     435
     436.avatar-history-actions button.delete:not(.disabled):hover {
     437    background-color: #800;
     438    color: #fff;
     439}
     440
     441.avatar-history-actions button.delete:not(.disabled) {
     442    background-color: #fdc;
     443    border: 1px solid #a00;
     444    color: #800;
     445}
     446
    400447@media screen and (min-width: 810px) {
    401448
  • trunk/src/bp-core/js/avatar.js

    r12856 r13179  
    4040            this.Attachment = new Backbone.Model();
    4141
     42            // The Avatars history.
     43            this.historicAvatars = null;
     44
    4245            // Wait till the queue is reset.
    4346            bp.Uploader.filesQueue.on( 'reset', this.cropView, this );
     
    115118                case 'delete':
    116119                    this.deleteView();
     120                    break;
     121
     122                case 'recycle':
     123                    this.recycleView();
    117124                    break;
    118125            }
     
    304311        },
    305312
    306         deleteView:function() {
     313        deleteView: function() {
    307314            // Create the delete model.
    308315            var delete_model = new Backbone.Model( _.pick( BP_Uploader.settings.defaults.multipart_params.bp_params,
     
    333340            }
    334341
    335             // Remove the avatar !
     342            // Remove the avatar!
    336343            bp.ajax.post( 'bp_avatar_delete', {
    337344                json:          true,
     
    408415
    409416            this.warning.inject( '.bp-avatar-status' );
     417        },
     418
     419        recycleView: function() {
     420            if ( ! this.historicAvatars ) {
     421                this.historicAvatars = new Backbone.Collection( BP_Uploader.settings.history );
     422            }
     423
     424            // Create the recycle view.
     425            var recycleView = new bp.Views.RecycleAvatar( { collection: this.historicAvatars } );
     426
     427            // Add it to views.
     428            this.views.add( { id: 'recycle', view: recycleView } );
     429
     430            // Display it.
     431            recycleView.inject( '.bp-avatar' );
     432        },
     433
     434        recycleHistoricAvatar: function( model ) {
     435            var self = this;
     436            model.set( 'selected', false );
     437
     438            // Recycle the avatar.
     439            bp.ajax.post( 'bp_avatar_recycle_previous', {
     440                json: true,
     441                item_id: BP_Uploader.settings.defaults.multipart_params.bp_params.item_id,
     442                avatar_id: model.get( 'id' ),
     443                object: BP_Uploader.settings.defaults.multipart_params.bp_params.object,
     444                nonce: BP_Uploader.settings.historyNonces.recylePrevious
     445            } ).done( function( response ) {
     446                if ( response.historicalAvatar ) {
     447                    model.collection.add( response.historicalAvatar );
     448                }
     449
     450                model.collection.remove( model );
     451
     452                if ( response.feedback_code ) {
     453                    var avatarStatus = new bp.Views.AvatarStatus( {
     454                        value : BP_Uploader.strings.feedback_messages[ response.feedback_code ],
     455                        type : 'success'
     456                    } );
     457
     458                    self.views.add( {
     459                        id   : 'status',
     460                        view : avatarStatus
     461                    } );
     462
     463                    avatarStatus.inject( '.bp-avatar-status' );
     464                }
     465
     466                // Update each avatars of the page.
     467                $( '.' + BP_Uploader.settings.defaults.multipart_params.bp_params.object + '-' + response.item_id + '-avatar' ).each( function() {
     468                    $( this ).prop( 'src', response.avatar );
     469                } );
     470
     471            } ).fail( function( response ) {
     472                var error_message = BP_Uploader.strings.default_error;
     473                if ( response && response.message ) {
     474                    error_message = response.message;
     475                }
     476
     477                var avatarStatus = new bp.Views.AvatarStatus( {
     478                    value : error_message,
     479                    type : 'error'
     480                } );
     481
     482                self.views.add( {
     483                    id   : 'status',
     484                    view : avatarStatus
     485                } );
     486
     487                avatarStatus.inject( '.bp-avatar-status' );
     488            } );
     489        },
     490
     491        deletePreviousAvatar: function( model ) {
     492            var self = this;
     493            model.set( 'selected', false );
     494
     495            // Recycle the avatar.
     496            bp.ajax.post( 'bp_avatar_delete_previous', {
     497                json: true,
     498                item_id: BP_Uploader.settings.defaults.multipart_params.bp_params.item_id,
     499                avatar_id: model.get( 'id' ),
     500                object: BP_Uploader.settings.defaults.multipart_params.bp_params.object,
     501                nonce: BP_Uploader.settings.historyNonces.deletePrevious
     502            } ).done( function( response ) {
     503                model.collection.remove( model );
     504
     505                if ( response.feedback_code ) {
     506                    var avatarStatus = new bp.Views.AvatarStatus( {
     507                        value : BP_Uploader.strings.feedback_messages[ response.feedback_code ],
     508                        type : 'success'
     509                    } );
     510
     511                    self.views.add( {
     512                        id   : 'status',
     513                        view : avatarStatus
     514                    } );
     515
     516                    avatarStatus.inject( '.bp-avatar-status' );
     517                }
     518
     519            } ).fail( function( response ) {
     520                var error_message = BP_Uploader.strings.default_error;
     521                if ( response && response.message ) {
     522                    error_message = response.message;
     523                }
     524
     525                var avatarStatus = new bp.Views.AvatarStatus( {
     526                    value : error_message,
     527                    type : 'error'
     528                } );
     529
     530                self.views.add( {
     531                    id   : 'status',
     532                    view : avatarStatus
     533                } );
     534
     535                avatarStatus.inject( '.bp-avatar-status' );
     536            } );
    410537        }
    411538    };
     
    678805    } );
    679806
     807    bp.Views.HistoryAvatarsItem = bp.View.extend( {
     808        tagName: 'tr',
     809        className: 'historic-avatar',
     810        template: bp.template( 'bp-avatar-recycle-history-item' ),
     811
     812        events: {
     813            'change input[name="avatar_id"]': 'selectAvatar'
     814        },
     815
     816        initialize: function() {
     817            this.model.on( 'change:selected', this.toggleSelection, this );
     818            this.model.on( 'remove', this.clearView, this );
     819        },
     820
     821        toggleSelection: function( model, selected ) {
     822            if ( true === selected ) {
     823                this.$el.parent().find( '#' + model.get( 'id' ) ).addClass( 'selected' );
     824            } else {
     825                this.$el.parent().find( '#' + model.get( 'id' ) ).removeClass( 'selected' );
     826                this.$el.parent().find( '#avatar_' + model.get( 'id' ) ).prop( 'checked', false );
     827            }
     828        },
     829
     830        selectAvatar: function( event ) {
     831            event.preventDefault();
     832
     833            if ( event.currentTarget.checked ) {
     834                var self = this;
     835
     836                _.each( this.model.collection.models, function( model ) {
     837                    self.$el.parent().find( '#' + model.id ).removeClass( 'selected' );
     838                    model.set( 'selected', false, { silent: true } );
     839                } );
     840
     841                this.model.set( 'selected', true );
     842            }
     843        },
     844
     845        clearView: function() {
     846            this.views.view.remove();
     847        }
     848    } );
     849
     850    bp.Views.RecycleAvatar = bp.View.extend( {
     851        tagName: 'div',
     852        id: 'bp-avatars-history-container',
     853        template: bp.template( 'bp-avatar-recycle' ),
     854
     855        events: {
     856            'click button.avatar-history-action': 'doAvatarAction'
     857        },
     858
     859        initialize: function() {
     860            _.each( this.collection.models, function( model ) {
     861                this.views.add( '#bp-avatars-history-list', new bp.Views.HistoryAvatarsItem( { model: model } ) );
     862            }, this );
     863
     864            this.collection.on( 'change:selected', this.updateButtonStatus, this );
     865            this.collection.on( 'add', this.addView, this );
     866        },
     867
     868        addView: function( model ) {
     869            this.views.add( '#bp-avatars-history-list', new bp.Views.HistoryAvatarsItem( { model: model } ) );
     870        },
     871
     872        updateButtonStatus: function( model ) {
     873            if ( true === model.get( 'selected' ) ) {
     874                this.$el.find( 'button.disabled' ).removeClass( 'disabled' );
     875            }
     876        },
     877
     878        doAvatarAction: function( event ) {
     879            event.preventDefault();
     880            var buttonClasses = event.currentTarget.classList,
     881                selected = this.collection.findWhere( { selected: true } );
     882
     883            if ( ! buttonClasses.contains( 'disabled' ) && selected ) {
     884                // Make sure it's not possible to fire 2 actions at the same time.
     885                this.$el.find( 'button.avatar-history-action' ).addClass( 'disabled' );
     886
     887                if ( buttonClasses.contains( 'recycle' ) ) {
     888                    bp.Avatar.recycleHistoricAvatar( selected );
     889                } else if ( buttonClasses.contains( 'delete' ) ) {
     890                    bp.Avatar.deletePreviousAvatar( selected );
     891                }
     892            }
     893        }
     894    } );
     895
    680896    bp.Avatar.start();
    681897
  • trunk/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/index.php

    r12082 r13179  
    3434<?php bp_attachments_get_template_part( 'avatars/camera' ); ?>
    3535
     36<?php bp_attachments_get_template_part( 'avatars/recycle' ); ?>
     37
    3638<script id="tmpl-bp-avatar-delete" type="text/html">
    3739    <# if ( 'user' === data.object ) { #>
  • trunk/src/bp-templates/bp-nouveau/buddypress/assets/_attachments/avatars/index.php

    r12156 r13179  
    3131<?php bp_attachments_get_template_part( 'avatars/camera' ); ?>
    3232
     33<?php bp_attachments_get_template_part( 'avatars/recycle' ); ?>
     34
    3335<script id="tmpl-bp-avatar-delete" type="text/html">
    3436    <# if ( 'user' === data.object ) { #>
Note: See TracChangeset for help on using the changeset viewer.