diff --git a/src/styles/components/_buddycondensed.scss b/src/styles/components/_buddycondensed.scss index 8a003bca..1712e3b2 100644 --- a/src/styles/components/_buddycondensed.scss +++ b/src/styles/components/_buddycondensed.scss @@ -42,17 +42,14 @@ display: none; position: fixed; top: 49px; - &:hover { - display: block; - } } .desc { - display: inline-block; + display: none; font-weight: bold; padding-left: 10px; cursor: default; - &:hover ~ .overDefaultDisplayNum { - display: block; + &.overNum { + display: inline-block; } } .actions { @@ -87,8 +84,5 @@ .buddyPicture { cursor: pointer; overflow: visible; - &:hover .actions { - display: inline-block; - } } } diff --git a/static/js/directives/buddycondensed.js b/static/js/directives/buddycondensed.js index b794d7c9..5da426f5 100644 --- a/static/js/directives/buddycondensed.js +++ b/static/js/directives/buddycondensed.js @@ -18,13 +18,21 @@ * along with this program. If not, see . * */ -define(['angular', 'text!partials/buddycondensed.html'], function(angular, template) { +define(['angular', 'jquery', 'text!partials/buddycondensed.html', 'hoverIntent'], function(angular, $, template) { // buddycondensed return [function() { - var controller = ['$scope', 'mediaStream', 'contacts', function($scope, mediaStream, contacts) { + var controller = ['$scope', 'mediaStream', 'contacts', 'buddyData', function($scope, mediaStream, contacts, buddyData) { var buddycondensed = []; + var getContactSessionId = function(userid) { + var session = null; + var scope = buddyData.lookup(userid, false, false); + if (scope) { + session = scope.session.get(); + } + return session && session.Id ? session.Id : null; + }; var empty = function(x) { return x === null || x === undefined || isNaN(x) || x === ""; }; @@ -50,6 +58,7 @@ define(['angular', 'text!partials/buddycondensed.html'], function(angular, templ }; var joined = function(buddy) { buddycondensed.push(buddy); + buddycondensed.push(angular.extend({}, buddy)); }; var left = function(id) { for (var i in buddycondensed) { @@ -77,6 +86,15 @@ define(['angular', 'text!partials/buddycondensed.html'], function(angular, templ } $scope.$apply(); }; + $scope.call = function(userid) { + mediaStream.webrtc.doCall(getContactSessionId(userid)); + }; + $scope.chat = function(userid) { + $scope.$emit("startchat", getContactSessionId(userid), { + autofocus: true, + restore: true + }); + }; $scope.listDefault = function() { if(buddycondensed.length >= $scope.maxBuddiesToShow) { return buddycondensed.slice(0, $scope.maxBuddiesToShow); @@ -118,7 +136,52 @@ define(['angular', 'text!partials/buddycondensed.html'], function(angular, templ }); }]; - var link = function($scope, elem, attrs, ctrl) {}; + var link = function($scope, elem, attrs, ctrl) { + var overDefaultDisplayNum = elem.find(".overDefaultDisplayNum"); + var desc = elem.find(".desc"); + var aboveElem1 = false; + var aboveElem2 = false; + var outMoreBuddy = function(event) { + //console.log('out', event.currentTarget.className); + if (event.currentTarget === desc.get(0)) { + aboveElem1 = false; + } else if (event.currentTarget === overDefaultDisplayNum.get(0)) { + aboveElem2 = false; + } + if(!aboveElem1 && !aboveElem2) { + overDefaultDisplayNum.hide(); + } + }; + var overMoreBuddy = function(event) { + //console.log('out', event.currentTarget.className); + if (event.currentTarget === desc.get(0)) { + aboveElem1 = true; + } else if (event.currentTarget === overDefaultDisplayNum.get(0)) { + aboveElem2 = true; + } + overDefaultDisplayNum.show(); + }; + elem.hoverIntent({ + over: overMoreBuddy, + out: outMoreBuddy, + timeout: 1000, + selector: '.desc, .overDefaultDisplayNum' + }); + var overBuddyPicture = function(event) { + //console.log('overBuddyPicture', event.currentTarget.className); + $(event.currentTarget).find(".actions").css("display", "inline-block"); + }; + var outBuddyPicture = function(event) { + //console.log('outBuddyPicture', event.currentTarget.className); + $(event.currentTarget).find(".actions").css("display", "none"); + }; + elem.hoverIntent({ + over: overBuddyPicture, + out: outBuddyPicture, + timeout: 100, + selector: '.buddyPicture' + }); + }; return { restrict: 'E', diff --git a/static/js/libs/jquery/jquery.hoverIntent.js b/static/js/libs/jquery/jquery.hoverIntent.js new file mode 100644 index 00000000..ac2f1192 --- /dev/null +++ b/static/js/libs/jquery/jquery.hoverIntent.js @@ -0,0 +1,115 @@ +/*! + * hoverIntent v1.8.1 // 2014.08.11 // jQuery v1.9.1+ + * http://cherne.net/brian/resources/jquery.hoverIntent.html + * + * You may use hoverIntent under the terms of the MIT license. Basically that + * means you are free to use hoverIntent as long as this header is left intact. + * Copyright 2007, 2014 Brian Cherne + */ + +/* hoverIntent is similar to jQuery's built-in "hover" method except that + * instead of firing the handlerIn function immediately, hoverIntent checks + * to see if the user's mouse has slowed down (beneath the sensitivity + * threshold) before firing the event. The handlerOut function is only + * called after a matching handlerIn. + * + * // basic usage ... just like .hover() + * .hoverIntent( handlerIn, handlerOut ) + * .hoverIntent( handlerInOut ) + * + * // basic usage ... with event delegation! + * .hoverIntent( handlerIn, handlerOut, selector ) + * .hoverIntent( handlerInOut, selector ) + * + * // using a basic configuration object + * .hoverIntent( config ) + * + * @param handlerIn function OR configuration object + * @param handlerOut function OR selector for delegation OR undefined + * @param selector selector OR undefined + * @author Brian Cherne + */ +(function($) { + $.fn.hoverIntent = function(handlerIn,handlerOut,selector) { + + // default configuration values + var cfg = { + interval: 100, + sensitivity: 6, + timeout: 0 + }; + + if ( typeof handlerIn === "object" ) { + cfg = $.extend(cfg, handlerIn ); + } else if ($.isFunction(handlerOut)) { + cfg = $.extend(cfg, { over: handlerIn, out: handlerOut, selector: selector } ); + } else { + cfg = $.extend(cfg, { over: handlerIn, out: handlerIn, selector: handlerOut } ); + } + + // instantiate variables + // cX, cY = current X and Y position of mouse, updated by mousemove event + // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval + var cX, cY, pX, pY; + + // A private function for getting mouse position + var track = function(ev) { + cX = ev.pageX; + cY = ev.pageY; + }; + + // A private function for comparing current and previous mouse position + var compare = function(ev,ob) { + ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); + // compare mouse positions to see if they've crossed the threshold + if ( Math.sqrt( (pX-cX)*(pX-cX) + (pY-cY)*(pY-cY) ) < cfg.sensitivity ) { + $(ob).off("mousemove.hoverIntent",track); + // set hoverIntent state to true (so mouseOut can be called) + ob.hoverIntent_s = true; + return cfg.over.apply(ob,[ev]); + } else { + // set previous coordinates for next time + pX = cX; pY = cY; + // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs) + ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval ); + } + }; + + // A private function for delaying the mouseOut function + var delay = function(ev,ob) { + ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); + ob.hoverIntent_s = false; + return cfg.out.apply(ob,[ev]); + }; + + // A private function for handling mouse 'hovering' + var handleHover = function(e) { + // copy objects to be passed into t (required for event object to be passed in IE) + var ev = $.extend({},e); + var ob = this; + + // cancel hoverIntent timer if it exists + if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); } + + // if e.type === "mouseenter" + if (e.type === "mouseenter") { + // set "previous" X and Y position based on initial entry point + pX = ev.pageX; pY = ev.pageY; + // update "current" X and Y position based on mousemove + $(ob).on("mousemove.hoverIntent",track); + // start polling interval (self-calling timeout) to compare mouse coordinates over time + if (!ob.hoverIntent_s) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );} + + // else e.type == "mouseleave" + } else { + // unbind expensive mousemove event + $(ob).off("mousemove.hoverIntent",track); + // if hoverIntent state is true, then call the mouseOut function after the specified delay + if (ob.hoverIntent_s) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );} + } + }; + + // listen for mouseenter and mouseleave + return this.on({'mouseenter.hoverIntent':handleHover,'mouseleave.hoverIntent':handleHover}, cfg.selector); + }; +})(jQuery); diff --git a/static/js/main.js b/static/js/main.js index a54270a7..0bf3104f 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -43,6 +43,7 @@ require.config({ 'avltree': 'libs/avltree', 'injectCSS': 'libs/jquery/jquery.injectCSS', 'mobile-events': 'libs/jquery/jquery.mobile-events', + 'hoverIntent': 'libs/jquery/jquery.hoverIntent', 'jed': 'libs/jed', 'audiocontext': 'libs/audiocontext', 'rAF': 'libs/rAF', @@ -112,6 +113,10 @@ require.config({ deps: ['jquery'], exports: '$' }, + 'hoverIntent': { + deps: ['jquery'], + exports: '$' + }, 'sjcl': { exports: 'sjcl' }, diff --git a/static/partials/buddycondensed.html b/static/partials/buddycondensed.html index 846c72e1..5ca3f45e 100644 --- a/static/partials/buddycondensed.html +++ b/static/partials/buddycondensed.html @@ -1,6 +1,6 @@
- + {{listOverDefault().length - maxBuddiesToShow}} {{_("more")}} + + {{listOverDefault().length}} {{_("more")}}
@@ -9,8 +9,8 @@ {{buddy.Userid|displayName}}
- - + +
@@ -24,8 +24,8 @@ {{buddy.Userid|displayName}}
- - + +