diff options
author | Lauri Ojansivu <x@xet7.org> | 2019-11-02 16:12:40 +0200 |
---|---|---|
committer | Lauri Ojansivu <x@xet7.org> | 2019-11-02 16:12:40 +0200 |
commit | 3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a (patch) | |
tree | e035c173de03a19ba61e718035609d1881edfd0c /client/components/cards | |
parent | 92efb8bec4744d7eacb134109a325a2c960790cb (diff) | |
download | wekan-3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a.tar.gz wekan-3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a.tar.bz2 wekan-3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a.zip |
Assignee field like Jira #2452 , in progress.
Added features:
- Assignee can now be added and removed.
- Avatar icon is at card and assignee details
TODO:
- When selecting new assignee (+) icon, list does not yet show avatars and names who to add.
There is empty avatar without name.
Thanks to xet7 !
Diffstat (limited to 'client/components/cards')
-rw-r--r-- | client/components/cards/cardDetails.jade | 40 | ||||
-rw-r--r-- | client/components/cards/cardDetails.js | 104 | ||||
-rw-r--r-- | client/components/cards/cardDetails.styl | 120 |
3 files changed, 262 insertions, 2 deletions
diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 639c7742..ad8010e4 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -76,7 +76,7 @@ template(name="cardDetails") .card-details-item.card-details-item-assignees h3.card-details-item-title {{_ 'assignee'}} each getAssignees - +userAvatar(userId=this cardId=../_id) + +userAvatarAssignee(userId=this cardId=../_id) | {{! XXX Hack to hide syntaxic coloration /// }} if canModifyCard a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}") @@ -307,7 +307,7 @@ template(name="cardMembersPopup") template(name="cardAssigneesPopup") ul.pop-over-list.js-card-assignee-list - each board.activeAssignees + each board.activeMembers li.item(class="{{#if isCardAssignee}}active{{/if}}") a.name.js-select-assignee(href="#") +userAvatarAssignee(userId=user._id) @@ -317,6 +317,42 @@ template(name="cardAssigneesPopup") if isCardAssignee i.fa.fa-check +template(name="userAvatarAssignee") + a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})") + if userData.profile.avatarUrl + img.avatar.avatar-image(src="{{userData.profile.avatarUrl}}") + else + +userAvatarAssigneeInitials(userId=userData._id) + + if showStatus + span.member-presence-status(class=presenceStatusClassName) + span.member-type(class=memberType) + + unless isSandstorm + if showEdit + if $eq currentUser._id userData._id + a.edit-avatar.js-change-avatar + i.fa.fa-pencil + +template(name="cardAssigneePopup") + .board-assignee-menu + .mini-profile-info + +userAvatar(userId=user._id showEdit=true) + .info + h3= user.profile.fullname + p.quiet @{{ user.username }} + ul.pop-over-list + if currentUser.isNotCommentOnly + li: a.js-remove-assignee {{_ 'remove-member-from-card'}} + + if $eq currentUser._id user._id + with currentUser + li: a.js-edit-profile {{_ 'edit-profile'}} + +template(name="userAvatarAssigneeInitials") + svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15") + text(x="50%" y="13" text-anchor="middle")= initials + template(name="cardMorePopup") p.quiet span.clearfix diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 6408db74..3b2873a2 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -344,6 +344,50 @@ BlazeComponent.extendComponent({ }, }).register('cardDetails'); +Template.cardDetails.helpers({ + userData() { + // We need to handle a special case for the search results provided by the + // `matteodem:easy-search` package. Since these results gets published in a + // separate collection, and not in the standard Meteor.Users collection as + // expected, we use a component parameter ("property") to distinguish the + // two cases. + const userCollection = this.esSearch ? ESSearchResults : Users; + return userCollection.findOne(this.userId, { + fields: { + profile: 1, + username: 1, + }, + }); + }, + + memberType() { + const user = Users.findOne(this.userId); + return user && user.isBoardAdmin() ? 'admin' : 'normal'; + }, + + presenceStatusClassName() { + const user = Users.findOne(this.userId); + const userPresence = presences.findOne({ userId: this.userId }); + if (user && user.isInvitedTo(Session.get('currentBoard'))) return 'pending'; + else if (!userPresence) return 'disconnected'; + else if (Session.equals('currentBoard', userPresence.state.currentBoardId)) + return 'active'; + else return 'idle'; + }, +}); + +Template.userAvatarAssigneeInitials.helpers({ + initials() { + const user = Users.findOne(this.userId); + return user && user.getInitials(); + }, + + viewPortWidth() { + const user = Users.findOne(this.userId); + return ((user && user.getInitials().length) || 1) * 12; + }, +}); + // We extends the normal InlinedForm component to support UnsavedEdits draft // feature. (class extends InlinedForm { @@ -809,3 +853,63 @@ EscapeActions.register( noClickEscapeOn: '.js-card-details,.board-sidebar,#header', }, ); + +Template.cardAssigneesPopup.events({ + 'click .js-select-assignee'(event) { + const card = Cards.findOne(Session.get('currentCard')); + const assigneeId = this.userId; + card.toggleAssignee(assigneeId); + event.preventDefault(); + }, +}); + +Template.cardAssigneePopup.helpers({ + userData() { + // We need to handle a special case for the search results provided by the + // `matteodem:easy-search` package. Since these results gets published in a + // separate collection, and not in the standard Meteor.Users collection as + // expected, we use a component parameter ("property") to distinguish the + // two cases. + const userCollection = this.esSearch ? ESSearchResults : Users; + return userCollection.findOne(this.userId, { + fields: { + profile: 1, + username: 1, + }, + }); + }, + + memberType() { + const user = Users.findOne(this.userId); + return user && user.isBoardAdmin() ? 'admin' : 'normal'; + }, + + presenceStatusClassName() { + const user = Users.findOne(this.userId); + const userPresence = presences.findOne({ userId: this.userId }); + if (user && user.isInvitedTo(Session.get('currentBoard'))) return 'pending'; + else if (!userPresence) return 'disconnected'; + else if (Session.equals('currentBoard', userPresence.state.currentBoardId)) + return 'active'; + else return 'idle'; + }, + + isCardAssignee() { + const card = Template.parentData(); + const cardAssignees = card.getAssignees(); + + return _.contains(cardAssignees, this.userId); + }, + + user() { + return Users.findOne(this.userId); + }, +}); + +Template.cardAssigneePopup.events({ + 'click .js-remove-assignee'() { + Cards.findOne(this.cardId).unassignAssignee(this.userId); + Popup.close(); + }, + 'click .js-edit-profile': Popup.open('editProfile'), +}); diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index 825e22e9..295a659d 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -1,5 +1,125 @@ @import 'nib' +// Assignee, code copied from wekan/client/users/userAvatar.styl + +avatar-radius = 50% + +.assignee + border-radius: 3px + display: block + position: relative + float: left + height: 30px + width: @height + margin: 0 4px 4px 0 + cursor: pointer + user-select: none + z-index: 1 + text-decoration: none + border-radius: avatar-radius + + .avatar + overflow: hidden + border-radius: avatar-radius + + &.avatar-assignee-initials + height: 70% + width: @height + padding: 15% + background-color: #dbdbdb + color: #444444 + position: absolute + + &.avatar-image + height: 100% + width: @height + + .assignee-presence-status + background-color: #b3b3b3 + border: 1px solid #fff + border-radius: 50% + height: 7px + width: @height + position: absolute + right: -1px + bottom: -1px + border: 1px solid white + z-index: 15 + + &.active + background: #64c464 + border-color: #daf1da + + &.idle + background: #e4e467 + border-color: #f7f7d4 + + &.disconnected + background: #bdbdbd + border-color: #ededed + + &.pending + background: #e44242 + border-color: #f1dada + + .edit-avatar + position: absolute + top: 0 + height: 100% + width: 100% + border-radius: avatar-radius + background: black + display: flex + align-items: center + justify-content: center + opacity: 0 + + &:hover + opacity: 0.6 + + i.fa-pencil + color: white + + + &.add-assignee + display: flex + align-items: center + justify-content: center + box-shadow: 0 0 0 2px darken(white, 25%) inset + + &:hover, &.is-active + box-shadow: 0 0 0 2px darken(white, 60%) inset + +.atMention + background: #dbdbdb + border-radius: 3px + padding: 1px 4px + margin: -1px 0 + display: inline-block + + &.me + background: #cfdfe8 + +.mini-profile-info + margin-top: 10px + + .info + padding-top: 5px + + h3, p + margin-bottom: 0 + padding-left: 0 + + p + padding-top: 0 + + .assignee + width: 50px + height: @width + margin-right: 10px + +// Other card details + .card-details padding: 0 flex-shrink: 0 |