From f4c80d1315c181300bd37ef5de98c365325bc130 Mon Sep 17 00:00:00 2001 From: Maxime Quandalle Date: Sat, 30 May 2015 15:50:48 +0200 Subject: [PATCH] Implement presence indicators --- .jshintrc | 2 + .meteor/packages | 3 + client/components/sidebar/sidebar.jade | 3 +- client/components/users/avatar.jade | 7 -- client/components/users/headerButtons.js | 5 - client/components/users/router.js | 1 - client/components/users/userAvatar.jade | 23 ++++ .../users/{helpers.js => userAvatar.js} | 21 ++-- .../users/{member.styl => userAvatar.styl} | 9 +- .../users/{form.styl => userForm.styl} | 1 - .../{headerButtons.jade => userHeader.jade} | 14 ++- client/components/users/userHeader.js | 39 +++++++ .../{templates.html => userProfile.html} | 39 ------- .../users/{events.js => userProfile.js} | 22 ---- client/config/router.js | 21 ++-- client/styles/temp.styl | 110 ------------------ collections/users.js | 12 +- server/publications/boards.js | 12 +- 18 files changed, 116 insertions(+), 228 deletions(-) delete mode 100644 client/components/users/avatar.jade delete mode 100644 client/components/users/headerButtons.js create mode 100644 client/components/users/userAvatar.jade rename client/components/users/{helpers.js => userAvatar.js} (53%) rename client/components/users/{member.styl => userAvatar.styl} (95%) rename client/components/users/{form.styl => userForm.styl} (99%) rename client/components/users/{headerButtons.jade => userHeader.jade} (71%) create mode 100644 client/components/users/userHeader.js rename client/components/users/{templates.html => userProfile.html} (75%) rename client/components/users/{events.js => userProfile.js} (63%) delete mode 100644 client/styles/temp.styl diff --git a/.jshintrc b/.jshintrc index bf6b5ffe5..a03f740f3 100644 --- a/.jshintrc +++ b/.jshintrc @@ -55,6 +55,8 @@ "Mousetrap": false, "Avatar": true, "Ps": true, + "Presence": true, + "Presences": true, // Our collections "Boards": true, diff --git a/.meteor/packages b/.meteor/packages index c04186c6e..48d8665ed 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -2,6 +2,9 @@ # # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. +# +# XXX Should we replace tmeasday:presence by 3stack:presence? Or maybe the +# packages will merge in the future? meteor-platform diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index 9dd47b0db..40e0dae31 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -31,10 +31,11 @@ template(name="membersWidget") userId=this.userId draggable=true size="small" - showBadges=true) + showStatus=true) unless isSandstorm if currentUser.isBoardAdmin a.js-open-manage-board-members + .clearfix template(name="labelsWidget") .board-widget.board-widget-labels diff --git a/client/components/users/avatar.jade b/client/components/users/avatar.jade deleted file mode 100644 index 70ef69e01..000000000 --- a/client/components/users/avatar.jade +++ /dev/null @@ -1,7 +0,0 @@ -template(name="userAvatar") - .member(class="{{class}} {{# if draggable }}js-member{{else}}js-member-on-card-menu{{/if}}" - title="{{userData.profile.name}} ({{userData.username}})") - +avatar(user=userData size=size) - if showBadges - span.member-status(class="{{# if userData.profile.status}}active{{/if}}") - span.member-type(class=memberType) diff --git a/client/components/users/headerButtons.js b/client/components/users/headerButtons.js deleted file mode 100644 index 70594fb58..000000000 --- a/client/components/users/headerButtons.js +++ /dev/null @@ -1,5 +0,0 @@ -Template.headerUserBar.events({ - 'click .js-sign-in': Popup.open('signup'), - 'click .js-log-in': Popup.open('login'), - 'click .js-open-header-member-menu': Popup.open('memberMenu') -}); diff --git a/client/components/users/router.js b/client/components/users/router.js index d59e174d2..6c4ab3b66 100644 --- a/client/components/users/router.js +++ b/client/components/users/router.js @@ -1,4 +1,3 @@ - _.each(['signIn', 'signUp', 'resetPwd', 'forgotPwd', 'enrollAccount', 'changePwd'], function(routeName) { AccountsTemplates.configureRoute(routeName, { diff --git a/client/components/users/userAvatar.jade b/client/components/users/userAvatar.jade new file mode 100644 index 000000000..a76c46179 --- /dev/null +++ b/client/components/users/userAvatar.jade @@ -0,0 +1,23 @@ +template(name="userAvatar") + .member(class="{{class}} {{# if draggable }}js-member{{else}}js-member-on-card-menu{{/if}}" + title="{{userData.profile.name}} ({{userData.username}})") + +avatar(user=userData size=size) + if showStatus + span.member-presence-status(class=presenceStatusClassName) + span.member-type(class=memberType) + + +template(name="userPopup") + .board-member-menu + .mini-profile-info + +userAvatar(user=user) + .info + h3.bottom + a.js-profile(href="{{ pathFor route='Profile' username=user.username }}")= user.profile.name + p.quiet.bottom @{{ user.username }} + +template(name="memberName") + a.inline-object.js-show-mem-menu(href="{{ pathFor route='Profile' username=user.username }}") + = user.profile.name + if username + | ({{ user.username }}) diff --git a/client/components/users/helpers.js b/client/components/users/userAvatar.js similarity index 53% rename from client/components/users/helpers.js rename to client/components/users/userAvatar.js index 33867298c..d7d221db1 100644 --- a/client/components/users/helpers.js +++ b/client/components/users/userAvatar.js @@ -9,19 +9,14 @@ Template.userAvatar.helpers({ var userId = this.userId || this.user._id; var user = Users.findOne(userId); return user && user.isBoardAdmin() ? 'admin' : 'normal'; - } -}); - -Template.setLanguagePopup.helpers({ - languages: function() { - return _.map(TAPi18n.getLanguages(), function(lang, tag) { - return { - tag: tag, - name: lang.name - }; - }); }, - isCurrentLanguage: function() { - return this.tag === TAPi18n.getLanguage(); + presenceStatusClassName: function() { + var userPresence = Presences.findOne({ userId: this.user._id }); + if (! userPresence) + return 'disconnected'; + else if (Session.equals('currentBoard', userPresence.state.currentBoardId)) + return 'active'; + else + return 'idle'; } }); diff --git a/client/components/users/member.styl b/client/components/users/userAvatar.styl similarity index 95% rename from client/components/users/member.styl rename to client/components/users/userAvatar.styl index 3dfdaa371..dfe591430 100644 --- a/client/components/users/member.styl +++ b/client/components/users/userAvatar.styl @@ -38,16 +38,17 @@ avatar-radius = 50% max-width: 100% max-height: 100% - .member-status + .member-presence-status background-color: #b3b3b3 border: 1px solid #fff border-radius: 50% - height: 8px + height: 7px width: @height position: absolute - right: 0px - bottom: 0px + right: -1px + bottom: -1px border: 1px solid white + z-index: 15 &.active background: #64c464 diff --git a/client/components/users/form.styl b/client/components/users/userForm.styl similarity index 99% rename from client/components/users/form.styl rename to client/components/users/userForm.styl index 845c810dd..5ecef7aaf 100644 --- a/client/components/users/form.styl +++ b/client/components/users/userForm.styl @@ -7,7 +7,6 @@ img width: 275px - .at-form margin: auto width: 275px diff --git a/client/components/users/headerButtons.jade b/client/components/users/userHeader.jade similarity index 71% rename from client/components/users/headerButtons.jade rename to client/components/users/userHeader.jade index 0a2f64cf7..eb3d265ca 100644 --- a/client/components/users/headerButtons.jade +++ b/client/components/users/userHeader.jade @@ -8,11 +8,6 @@ template(name="headerUserBar") = currentUser.username +userAvatar(user=currentUser) -template(name="memberHeader") - a.header-member.js-open-header-member-menu - span= currentUser.profile.name - +userAvatar(user=currentUser size="small") - template(name="memberMenuPopup") ul.pop-over-list li: a(href="{{pathFor route='Profile' username=currentUser.username}}") {{_ 'profile'}} @@ -21,3 +16,12 @@ template(name="memberMenuPopup") hr ul.pop-over-list li: a.js-logout {{_ 'log-out'}} + +template(name="setLanguagePopup") + ul.pop-over-list + each languages + li(class="{{# if isCurrentLanguage}}active{{/if}}") + a.js-set-language + = name + if isCurrentLanguage + i.fa.fa-check diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js new file mode 100644 index 000000000..3bb9e6234 --- /dev/null +++ b/client/components/users/userHeader.js @@ -0,0 +1,39 @@ +Template.headerUserBar.events({ + 'click .js-open-header-member-menu': Popup.open('memberMenu') +}); + +Template.setLanguagePopup.helpers({ + languages: function() { + return _.map(TAPi18n.getLanguages(), function(lang, tag) { + return { + tag: tag, + name: lang.name + }; + }); + }, + isCurrentLanguage: function() { + return this.tag === TAPi18n.getLanguage(); + } +}); + +Template.memberMenuPopup.events({ + 'click .js-language': Popup.open('setLanguage'), + 'click .js-logout': function(evt) { + evt.preventDefault(); + + Meteor.logout(function() { + Router.go('Home'); + }); + } +}); + +Template.setLanguagePopup.events({ + 'click .js-set-language': function(evt) { + Users.update(Meteor.userId(), { + $set: { + 'profile.language': this.tag + } + }); + evt.preventDefault(); + } +}); diff --git a/client/components/users/templates.html b/client/components/users/userProfile.html similarity index 75% rename from client/components/users/templates.html rename to client/components/users/userProfile.html index 5783eebf2..3d1f8c9b8 100644 --- a/client/components/users/templates.html +++ b/client/components/users/userProfile.html @@ -1,18 +1,3 @@ - - - - - - - diff --git a/client/components/users/events.js b/client/components/users/userProfile.js similarity index 63% rename from client/components/users/events.js rename to client/components/users/userProfile.js index b741532ff..040abed07 100644 --- a/client/components/users/events.js +++ b/client/components/users/userProfile.js @@ -1,25 +1,3 @@ -Template.memberMenuPopup.events({ - 'click .js-language': Popup.open('setLanguage'), - 'click .js-logout': function(evt) { - evt.preventDefault(); - - Meteor.logout(function() { - Router.go('Home'); - }); - } -}); - -Template.setLanguagePopup.events({ - 'click .js-set-language': function(evt) { - Users.update(Meteor.userId(), { - $set: { - 'profile.language': this.tag - } - }); - evt.preventDefault(); - } -}); - Template.profileEditForm.events({ 'click .js-edit-profile': function() { Session.set('ProfileEditForm', true); diff --git a/client/config/router.js b/client/config/router.js index ed9a069d6..8fa74bee1 100644 --- a/client/config/router.js +++ b/client/config/router.js @@ -24,6 +24,13 @@ Router.configure({ return this.redirect('atSignIn'); } + // We want to execute our EscapeActions.executeLowerThan method any time the + // route is changed, but not if the stays the same but only the parameters + // change (eg when a user is navigation from a card A to a card B). Iron- + // Router onBeforeAction is a reactive context (which is a bad desig choice + // as explained in + // https://github.com/meteorhacks/flow-router#routercurrent-is-evil) so we + // need to use Tracker.nonreactive Tracker.nonreactive(function() { if (! options.noEscapeActions && ! (previousRoute && previousRoute.options.noEscapeActions)) @@ -35,17 +42,3 @@ Router.configure({ this.next(); } }); - -// We want to execute our EscapeActions.executeLowerThan method any time the -// route is changed, but not if the stays the same but only the parameters -// change (eg when a user is navigation from a card A to a card B). This is why -// we can’t put this function in the above `onBeforeAction` that is being run -// too many times, instead we register a dependency only on the route name and -// use Tracker.autorun. The following paragraph explains the problem quite well: -// https://github.com/meteorhacks/flow-router#routercurrent-is-evil -// Tracker.autorun(function(computation) { -// routeName.get(); -// if (! computation.firstRun) { -// EscapeActions.executeLowerThan('inlinedForm'); -// } -// }); diff --git a/client/styles/temp.styl b/client/styles/temp.styl deleted file mode 100644 index 9dab78025..000000000 --- a/client/styles/temp.styl +++ /dev/null @@ -1,110 +0,0 @@ -/** - * We should merge these declarations in the appropriate stylus files. - */ - -.dn { - display:none; -} - -.header-btn-btn { - padding-left:23px!important; -} - -.bgnone { - background:none!important; -} - -.tac { - text-align:center; - - h1 { - font-size: 2em; - } -} - -.tdn { - text-decoration:none; -} - -.header-member { - min-width:105px!important; - text-align:center; -} - -.primarys { - font-size:20px; - line-height: 1.44em; - padding: .6em 1.3em!important; - border-radius: 3px!important; - box-shadow: 0 2px 0 #4d4d4d!important; -} - -.layout-twothirds-center { - display: block; - max-width: 585px; - margin: 0 auto; - position: relative; - font-size:20px; - line-height: 100px; -} - -#WindowTitleEdit .single-line, .single-line2 { - overflow: hidden; - word-wrap: break-word; - resize: none; - height: 60px; -} - -.single-line2 { - overflow: hidden; - word-wrap: break-word; - resize: none; - height: 108px; -} - -#header-search { - float: left; - margin: 1px 8px 0 0; - position: relative; - z-index: 1; - - label { - display:none; - } - input[type="text"] { - background:rgba(255,255,255,0.5); - border-top-left-radius:3px; - border-top-right-radius:0; - border-bottom-right-radius:0; - border-bottom-left-radius:3px; - border:none; - float:left; - font-size:13px; - height:29px; - min-height:29px; - line-height:19px; - width:160px; - margin:0; - - &:hover{ - background:rgba(255,255,255,0.7); - } - - &:focus{ - background:#e8ebee; - -webkit-box-shadow:none; - box-shadow:none - } - } - - .header-btn{ - border-top-left-radius:0; - border-top-right-radius:3px; - border-bottom-right-radius:3px; - border-bottom-left-radius:0 - } - - input[type="submit"]{ - display:none - } -} diff --git a/collections/users.js b/collections/users.js index 6e2c6bdcc..2f44e19e8 100644 --- a/collections/users.js +++ b/collections/users.js @@ -43,9 +43,6 @@ Users.helpers({ Users.before.insert(function(userId, doc) { doc.profile = doc.profile || {}; - - // connect profile.status default - doc.profile.status = 'offline'; }); if (Meteor.isServer) { @@ -110,3 +107,12 @@ if (Meteor.isServer) { }); }); } + +// Presence indicator +if (Meteor.isClient) { + Presence.state = function() { + return { + currentBoardId: Session.get('currentBoard') + }; + }; +} diff --git a/server/publications/boards.js b/server/publications/boards.js index 6a4e476dc..103e90806 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -108,14 +108,20 @@ Meteor.publishComposite('board', function(boardId, slug) { }, // Board members. This publication also includes former board members that - // are no more members of the board but may have some activities attached - // to them. + // aren't members anymore but may have some activities attached to them in + // the history. { find: function(board) { return Users.find({ _id: { $in: _.pluck(board.members, 'userId') } }); - } + }, + // Presence indicators + children: [{ + find: function(user) { + return Presences.find({userId: user._id}); + } + }] } ] };