浏览代码

(Re-)implement default avatar using user initials

We use a embedded svg to scale the initials text to its container
size. The user is free to overwrite its initials in the profile form.
Maxime Quandalle 10 年之前
父节点
当前提交
765b0168ea

+ 17 - 15
client/components/main/popup.styl

@@ -218,22 +218,24 @@ $popupWidth = 300px
   .pop-over-list
     padding-top: 8px
 
-.mini-profile-info
-  margin-top: 8px
-  min-height: 56px
-  position: relative
+  .miniprofile-header
+    margin-top: 8px
+    min-height: 56px
+    position: relative
 
-  .member-large
-    position: absolute
-    top: 2px
-    left: 2px
+    .member
+      position: absolute
+      top: 2px
+      left: 2px
+      height: 50px
+      width: @height
 
-  .info
-    margin: 0 0 0 64px
-    word-wrap: break-word
+    .info
+      margin: 0 0 0 64px
+      word-wrap: break-word
 
-    h3 a
-      text-decoration: none
+      h3 a
+        text-decoration: none
 
-      &:hover
-        text-decoration: underline
+        &:hover
+          text-decoration: underline

+ 9 - 7
client/components/sidebar/sidebar.jade

@@ -47,13 +47,15 @@ template(name="labelsWidget")
         i.fa.fa-plus
 
 template(name="memberPopup")
-  .board-member-menu: .mini-profile-info
-    +userAvatar(user=user)
-    .info
-      h3.bottom
-        .js-profile
-          = user.profile.name
-      p.quiet.bottom @#{user.username}
+  .board-member-menu
+    .miniprofile-header
+      +userAvatar(userId=user._id)
+      .info
+        h3.bottom
+          .js-profile
+            = user.profile.name
+        p.quiet.bottom @#{user.username}
+
     if currentUser.isBoardMember
       ul.pop-over-list
         li

+ 18 - 4
client/components/users/userAvatar.jade

@@ -1,15 +1,22 @@
 template(name="userAvatar")
-  .member.js-member(title="{{userData.profile.fullname}} ({{userData.username}})")
+  a.member.js-member(title="{{userData.profile.fullname}} ({{userData.username}})")
     if userData.profile.avatarUrl
       img.avatar.avatar-image(src=userData.profile.avatarUrl)
+    else
+      +userAvatarInitials(userId=userData._id)
+
     if showStatus
       span.member-presence-status(class=presenceStatusClassName)
       span.member-type(class=memberType)
 
+template(name="userAvatarInitials")
+  svg.avatar.avatar-initials(viewBox="0 0 {{viewPortWidth}} 15")
+    text(x="0" y="13")= initials
+
 template(name="userPopup")
   .board-member-menu
     .mini-profile-info
-      +userAvatar(user=user)
+      +userAvatar(userId=user._id)
       .info
         h3.bottom
           a.js-profile(href="{{pathFor route='Profile' username=user.username}}")= user.profile.name
@@ -25,8 +32,8 @@ template(name="changeAvatarPopup")
   ul.pop-over-list
     each uploadedAvatars
       li: a.js-select-avatar
-        .member: .avatar
-          img.avatar-image(src="{{url avatarUrlOptions}}")
+        .member
+          img.avatar.avatar-image(src="{{url avatarUrlOptions}}")
         | Uploaded avatar
         if isSelected
           i.fa.fa-check
@@ -36,6 +43,13 @@ template(name="changeAvatarPopup")
               | Delete
             |  -
           = original.name
+    li: a.js-select-initials
+      .member
+        +userAvatarInitials(userId=currentUser._id)
+      | Initials
+      if noAvatarUrl
+        i.fa.fa-check
+       p.sub-name Default avatar
   input.hide.js-upload-avatar-input(accept="image/*;capture=camera" type="file")
   button.full.js-upload-avatar
     i.fa.fa-upload

+ 21 - 0
client/components/users/userAvatar.js

@@ -26,6 +26,18 @@ Template.userAvatar.helpers({
   }
 });
 
+Template.userAvatarInitials.helpers({
+  initials: function() {
+    var user = Users.findOne(this.userId);
+    return user && user.getInitials();
+  },
+
+  viewPortWidth: function() {
+    var user = Users.findOne(this.userId);
+    return (user && user.getInitials().length || 1) * 12;
+  }
+});
+
 BlazeComponent.extendComponent({
   template: function() {
     return 'changeAvatarPopup';
@@ -49,6 +61,12 @@ BlazeComponent.extendComponent({
     return avatarUrl === currentAvatarUrl;
   },
 
+  noAvatarUrl: function() {
+    var userProfile = Meteor.user().profile;
+    var avatarUrl = userProfile && userProfile.avatarUrl;
+    return ! avatarUrl;
+  },
+
   setAvatar: function(avatarUrl) {
     Meteor.users.update(Meteor.userId(), {
       $set: {
@@ -84,6 +102,9 @@ BlazeComponent.extendComponent({
         var avatarUrl = this.currentData().url(this.avatarUrlOptions());
         this.setAvatar(avatarUrl);
       },
+      'click .js-select-initials': function() {
+        this.setAvatar('');
+      },
       'click .js-delete-avatar': function() {
         Avatars.remove(this.currentData()._id);
       }

+ 9 - 15
client/components/users/userAvatar.styl

@@ -5,11 +5,11 @@ avatar-radius = 50%
 .member
   border-radius: 3px
   display: block
+  position: relative
   float: left
   height: 30px
   width: @height
   margin: 0 4px 4px 0
-  position: relative
   cursor: pointer
   user-select: none
   z-index: 1
@@ -17,26 +17,20 @@ avatar-radius = 50%
   border-radius: avatar-radius
 
   .avatar
-    height: 100%
-    width: @height
-    display: flex
-    align-items: center
-    justify-content: center
     overflow: hidden
     border-radius: avatar-radius
 
-    .avatar-initials
-      font-weight: bold
-      max-width: 100%
-      max-height: 100%
-      font-size: 14px
-      line-height: 200%
+    &.avatar-initials
+      height: 70%
+      width: @height
+      padding: 15%
       background-color: #dbdbdb
       color: #444444
+      position: absolute
 
-    .avatar-image
-      max-width: 100%
-      max-height: 100%
+    &.avatar-image
+      height: 100%
+      width: @height
 
   .member-presence-status
     background-color: #b3b3b3

+ 17 - 2
collections/users.js

@@ -31,13 +31,28 @@ Users.helpers({
       return _.where(board.members, {userId: this._id})[0].isAdmin;
   },
 
+  getInitials: function() {
+    var profile = this.profile || {};
+    if (profile.initials)
+      return profile.initials;
+
+    else if (profile.fullname) {
+      return _.reduce(profile.fullname.split(/\s+/), function(memo, word) {
+        return memo + word[0];
+      }, '').toUpperCase();
+
+    } else {
+      return this.pseudo[0].toUpperCase();
+    }
+  },
+
   toggleBoardStar: function(boardId) {
-    var queryType = Meteor.user().hasStarred(boardId) ? '$pull' : '$addToSet';
+    var queryType = this.hasStarred(boardId) ? '$pull' : '$addToSet';
     var query = {};
     query[queryType] = {
       'profile.starredBoards': boardId
     };
-    Meteor.users.update(Meteor.userId(), query);
+    Meteor.users.update(this._id, query);
   }
 });