From 63bbbb4ab542d3d0f427be79fd7fa6706fa46142 Mon Sep 17 00:00:00 2001 From: Guangcong Luo Date: Wed, 8 May 2013 21:21:19 -0700 Subject: [PATCH] Implement notifications --- js/client-battle.js | 71 ++++++++--------------- js/client-chat.js | 5 +- js/client-mainmenu.js | 9 +++ js/client.js | 131 ++++++++++++++++++++++++++++++++++++++++-- style/client.css | 8 +++ 5 files changed, 166 insertions(+), 58 deletions(-) diff --git a/js/client-battle.js b/js/client-battle.js index c452bb899..4b57176da 100644 --- a/js/client-battle.js +++ b/js/client-battle.js @@ -106,9 +106,9 @@ this.$controls.html(''); return; - } else if (this.battle.playbackState === 2) { + } else if (this.battle.playbackState === 2 || this.battle.playbackState === 3) { - // battle is playing + // battle is playing or paused this.$controls.html('

'); return; @@ -169,7 +169,6 @@ } var battle = this.battle; - // this.notifying = false; if (!this.choice) { this.updateSide(this.request.side); @@ -375,7 +374,6 @@ controls += ''; this.$controls.html(controls); } - this.notifying = true; break; case 'switch': @@ -408,7 +406,6 @@ controls += ''; this.$controls.html(controls); this.selectSwitch(); - this.notifying = true; break; case 'team': @@ -455,7 +452,6 @@ controls += ''; this.$controls.html(controls); this.selectSwitch(); - this.notifying = true; break; default: @@ -484,23 +480,10 @@ var notifyObject = null; if (request.forceSwitch) { request.requestType = 'switch'; - notifyObject = { - type: 'yourSwitch', - room: this.id - }; } else if (request.teamPreview) { request.requestType = 'team'; - notifyObject = { - type: 'yourSwitch', - room: this.id - }; } else if (request.wait) { request.requestType = 'wait'; - } else { - notifyObject = { - type: 'yourMove', - room: this.id - }; } this.choice = null; @@ -508,30 +491,22 @@ if (request.side) { this.updateSideLocation(request.side, true); } - - // if (notifyObject) { - // var doNotify = function() { - // notify(notifyObject); - // this.notifying = true; - // updateRoomList(); - // }; - // if (this.battle.yourSide.initialized) { - // // The opponent's name is already known. - // notifyObject.user = this.battle.yourSide.name; - // doNotify(); - // } else { - // // The opponent's name isn't known yet, so wait until it is - // // known before sending the notification, so that it can include - // // the opponent's name. - // var callback = this.battle.stagnateCallback; - // this.battle.stagnateCallback = function(battle) { - // notifyObject.user = battle.yourSide.name; - // doNotify(); - // battle.stagnateCallback = callback; - // if (callback) callback(battle); - // }; - // } - // } + this.notifyRequest(); + }, + notifyRequest: function() { + var oName = this.battle.yourSide.name; + if (oName) oName = " against "+oName; + switch (this.request.requestType) { + case 'move': + this.notify("Your move!", "Move in your battle"+oName, 'choice'); + break; + case 'switch': + this.notify("Your switch!", "Switch in your battle"+oName, 'choice'); + break; + case 'team': + this.notify("Team preview!", "Choose your team order in your battle"+oName, 'choice'); + break; + } }, updateSideLocation: function(sideData, midBattle) { if (!sideData.id) return; @@ -634,7 +609,7 @@ } this.sendDecision('/choose '+this.choice.choices.join(',')); - this.notifying = false; + this.closeNotification('choice'); this.finalDecision = false; this.choice = {waiting: true}; @@ -665,7 +640,7 @@ } this.sendDecision('/choose '+this.choice.choices.join(',')); - this.notifying = false; + this.closeNotification('choice'); this.choice = {waiting: true}; this.updateControlsForPlayer(); @@ -691,14 +666,14 @@ } this.sendDecision('/team '+(pos)); - this.notifying = false; + this.closeNotification('choice'); this.choice = {waiting: true}; this.updateControlsForPlayer(); }, undoChoice: function(pos) { this.send('/undo'); - this.notifying = true; + this.notifyRequest(); this.choice = null; this.updateControlsForPlayer(); @@ -706,7 +681,7 @@ leaveBattle: function() { this.hideTooltip(); this.send('/leavebattle'); - this.notifying = false; + this.closeNotification('choice'); }, selectSwitch: function() { this.hideTooltip(); diff --git a/js/client-chat.js b/js/client-chat.js index ddd9efeec..fa026b9e8 100644 --- a/js/client-chat.js +++ b/js/client-chat.js @@ -482,10 +482,7 @@ var clickableName = '' + Tools.escapeHTML(name.substr(1)) + ''; var isHighlighted = this.getHighlight(message); if (isHighlighted) { - // notify({ - // type: 'highlight', - // user: name - // }); + this.notifyOnce("Mentioned by "+name, "\""+message+"\"", 'highlight'); } var highlight = isHighlighted ? ' style="background-color:#FDA;"' : ''; var chatDiv = '
'; diff --git a/js/client-mainmenu.js b/js/client-mainmenu.js index 547219ad1..63c4bdfcc 100644 --- a/js/client-mainmenu.js +++ b/js/client-mainmenu.js @@ -53,6 +53,8 @@ var oName = name; if (toId(name) === app.user.get('userid')) { oName = target; + } else { + this.notifyOnce("PM from "+oName, "\""+message+"\"", 'pm'); } var $pmWindow = this.openPM(oName, true); @@ -228,6 +230,9 @@ var name = $pmWindow.data('name'); if (data.challengesFrom[userid]) { var challenge = data.challengesFrom[userid]; + if (!$pmWindow.find('.challenge').length) { + self.notifyOnce("Challenge from "+name, "Format: "+Tools.escapeFormat(challenge.format), 'challenge:'+userid); + } var $challenge = self.openChallenge(name, $pmWindow); var buf = '

'+Tools.escapeHTML(name)+' wants to battle!

'; buf += '

'+self.renderFormats(challenge.format, true)+'

'; @@ -245,6 +250,7 @@ // or rejected it $challenge.remove(); } + self.closeNotification('challenge:'+userid); } } }); @@ -321,6 +327,7 @@ $challenge.html(buf); }, acceptChallenge: function(i, target) { + this.requestNotifications(); var $pmWindow = $(target).closest('.pm-window'); var userid = $pmWindow.data('userid'); @@ -338,6 +345,7 @@ app.send('/reject '+userid); }, makeChallenge: function(i, target) { + this.requestNotifications(); var $pmWindow = $(target).closest('.pm-window'); var userid = $pmWindow.data('userid'); var name = $pmWindow.data('name'); @@ -433,6 +441,7 @@ // buttons search: function(i, button) { + this.requestNotifications(); var $searchForm = $(button).closest('form'); if ($searchForm.find('.cancel').length) { return; diff --git a/js/client.js b/js/client.js index 580f128ed..7252476e2 100644 --- a/js/client.js +++ b/js/client.js @@ -20,6 +20,14 @@ return roomid.replace(/[^a-zA-Z0-9-]+/g, ''); } + // support Safari 6 notifications + // trying to use new-spec notifications on old versions of Chrome will crash + // certain older versions of Chrome and there doesn't appear to be a way to + // feature-detect them + if (!window.Notification && window.webkitNotification && navigator.userAgent.indexOf('Chrome') <= 0) { + window.Notification = window.webkitNotification; + } + var User = this.User = Backbone.Model.extend({ defaults: { name: '', @@ -204,6 +212,7 @@ routes: { '*path': 'dispatchFragment' }, + focused: true, initialize: function() { window.app = this; $('#main').html(''); @@ -229,6 +238,17 @@ self.addPopup('password', LoginPasswordPopup, {username: name}); }); + $(window).on('focus click', function() { + if (!self.focused) { + self.focused = true; + self.curRoom.dismissNotification(); + if (self.curSideRoom) self.curSideRoom.dismissNotification(); + } + }); + $(window).on('blur', function() { + self.focused = false; + }); + this.initializeConnection(); Backbone.history.start({pushState: true}); }, @@ -988,9 +1008,9 @@ var curId = (app.curRoom ? app.curRoom.id : ''); var curSideId = (app.curSideRoom ? app.curSideRoom.id : ''); - var buf = '
  • Home
  • '; - if (app.rooms.teambuilder) buf += '
  • Teambuilder
  • '; - if (app.rooms.ladder) buf += '
  • Ladder
  • '; + var buf = ''; var atLeastOne = false; var sideBuf = ''; @@ -1013,14 +1033,14 @@ name = ''+parts[0]+''+name+''; } if (room.isSideRoom) { - sideBuf += '
  • '+name+'
  • '; + sideBuf += '
  • '+name+'
  • '; continue; } if (!atLeastOne) { buf += '
      '; atLeastOne = true; } - buf += '
    • '+name+'
    • '; + buf += '
    • '+name+'
    • '; } if (atLeastOne) buf += '
    '; if (app.curSideRoom) { @@ -1113,7 +1133,7 @@ // }, - // graphical + // layout bestWidth: 659, show: function(position, leftWidth) { @@ -1130,6 +1150,7 @@ break; } this.$el.show(); + this.dismissNotification(); this.focus(); }, hide: function() { @@ -1141,9 +1162,105 @@ join: function() {}, leave: function() {}, + // notifications + + requestNotifications: function() { + if (window.Notification && Notification.requestPermission) { + Notification.requestPermission(function(permission) {}); + } + }, + notifications: null, + notify: function(title, body, tag, once) { + if (once && app.focused && (this === app.curRoom || this == app.curSideRoom)) return; + if (!tag) tag = 'message'; + if (!this.notifications) this.notifications = {}; + if (app.focused && (this === app.curRoom || this == app.curSideRoom)) { + this.notifications[tag] = {}; + } else if (window.Notification) { + // old one doesn't need to be closed; sending the tag should + // automatically replace the old notification + var notification = this.notifications[tag] = new Notification(title, { + lang: 'en', + body: body, + tag: this.id+':'+tag, + }); + var self = this; + notification.onclose = function() { + self.dismissNotification(tag); + }; + notification.onclick = function() { + self.clickNotification(tag); + }; + if (once) notification.psAutoclose = true; + } else if (window.macgap) { + macgap.growl.notify({ + title: title, + content: body + }); + this.notifications[tag] = {}; + if (once) notification.psAutoclose = true; + } else { + this.notifications[tag] = {}; + if (once) notification.psAutoclose = true; + } + app.topbar.updateTabbar(); + }, + notifyOnce: function(title, body, tag) { + return this.notify(title, body, tag, true); + }, + closeNotification: function(tag, alreadyClosed) { + if (!this.notifications) return; + if (!tag) { + for (tag in this.notifications) { + if (this.notifications[tag].close) this.notifications[tag].close(); + } + this.notifications = null; + app.topbar.updateTabbar(); + return; + } + if (!this.notifications[tag]) return; + if (!alreadyClosed && this.notifications[tag].close) this.notifications[tag].close(); + delete this.notifications[tag]; + if (_.isEmpty(this.notifications)) { + this.notifications = null; + app.topbar.updateTabbar(); + } + }, + dismissNotification: function(tag) { + if (!this.notifications) return; + if (!tag) { + for (tag in this.notifications) { + if (!this.notifications[tag].psAutoclose) continue; + if (this.notifications[tag].close) this.notifications[tag].close(); + delete this.notifications[tag]; + } + if (_.isEmpty(this.notifications)) { + this.notifications = null; + app.topbar.updateTabbar(); + } + return; + } + if (!this.notifications[tag]) return; + if (this.notifications[tag].close) this.notifications[tag].close(); + if (this.notifications[tag].psAutoclose) { + delete this.notifications[tag]; + if (_.isEmpty(this.notifications)) { + this.notifications = null; + app.topbar.updateTabbar(); + } + } else { + this.notifications[tag] = {}; + } + }, + clickNotification: function(tag) { + this.dismissNotification(tag); + app.focusRoom(this.id); + }, + // allocation destroy: function() { + this.closeNotification(); this.leave(); this.remove(); delete this.app; @@ -1332,11 +1449,13 @@ app.addPopup('user:avatars', AvatarsPopup); }, challenge: function() { + app.rooms[''].requestNotifications(); this.close(); app.focusRoom(''); app.rooms[''].challenge(this.data.name); }, pm: function() { + app.rooms[''].requestNotifications(); this.close(); app.focusRoom(''); app.rooms[''].focusPM(this.data.name); diff --git a/style/client.css b/style/client.css index 716420d10..e945fddd8 100644 --- a/style/client.css +++ b/style/client.css @@ -222,6 +222,14 @@ body { background: linear-gradient(top, #cfcfcf, #f2f2f2); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#cfcfcf', endColorstr='#f2f2f2'); } +.tabbar a.button.notifying { + border-color: #AA8866; + background: #e3c3a3; +} +.tabbar a.button.notifying:hover { + border-color: #604020; + background: #cfaf8f; +} .tabbar a.button.cur, .tabbar a.button.cur:hover { color: #777777;