make faction chat display almost work

This commit is contained in:
Stefan Breunig 2013-02-03 13:19:56 +01:00
parent 4da97f48e3
commit b7f1270ff6
10 changed files with 599 additions and 217 deletions

View File

@ -24,12 +24,13 @@ window.setupLargeImagePreview = function() {
});
}
window.setupStyles = function() {
$('head').append('<style>' +
[ '#largepreview.res img { border:2px solid '+COLORS[TEAM_RES]+'; } ',
'#largepreview.enl img { border:2px solid '+COLORS[TEAM_ENL]+'; } ',
'#largepreview.none img { border:2px solid '+COLORS[TEAM_NONE]+'; } ',
'#chatcontrols { bottom: '+(CHAT_SHRINKED+4)+'px; }',
'#chatcontrols { bottom: '+(CHAT_SHRINKED+24)+'px; }',
'#chat { height: '+CHAT_SHRINKED+'px; } ',
'#updatestatus { width:'+(SIDEBAR_WIDTH-2*4)+'px; } ',
'#sidebar, #gamestat, #gamestat span, input, ',
@ -82,21 +83,19 @@ window.setupMap = function() {
});
// map update status handling
map.on('zoomstart', function() { window.mapRunsUserAction = true });
map.on('movestart', function() { window.mapRunsUserAction = true });
map.on('zoomend', function() { window.mapRunsUserAction = false });
map.on('moveend', function() { window.mapRunsUserAction = false });
map.on('movestart zoomstart', function() { window.mapRunsUserAction = true });
map.on('moveend zoomend', function() { window.mapRunsUserAction = false });
// update map hooks
map.on('zoomstart', window.requests.abort);
map.on('zoomend', function() { window.startRefreshTimeout(500) });
map.on('movestart', window.requests.abort );
map.on('moveend', function() { window.startRefreshTimeout(500) });
map.on('movestart zoomstart', window.requests.abort);
map.on('moveend zoomend', function() { window.startRefreshTimeout(500) });
// run once on init
window.requestData();
window.startRefreshTimeout();
window.addResumeFunction(window.requestData);
window.requests.addRefreshFunction(window.requestData);
};
// renders player details into the website. Since the player info is
@ -151,7 +150,7 @@ function boot() {
window.setupLargeImagePreview();
window.updateGameScore();
window.setupPlayerStat();
window.setupChat();
window.chat.setup();
// read here ONCE, so the URL is only evaluated one time after the
// necessary data has been loaded.
urlPortal = getURLParam('pguid');

View File

@ -1,6 +1,6 @@
window.chat = function() {}
window.chat = function() {};
window.getOldestTimestampChat = function(public) {
window.chat.getOldestTimestamp = function(public) {
if(public) {
var a = $('#chatpublic time:first').data('timestamp');
var b = $('#chatbot time:first').data('timestamp');
@ -11,7 +11,7 @@ window.getOldestTimestampChat = function(public) {
}
}
window.getNewestTimestampChat = function(public) {
window.chat.getNewestTimestamp = function(public) {
if(public) {
var a = $('#chatpublic time:last').data('timestamp');
var b = $('#chatbot time:last').data('timestamp');
@ -22,7 +22,7 @@ window.getNewestTimestampChat = function(public) {
}
}
window.getPostDataForChat = function(public, getOlderMsgs) {
window.chat.genPostData = function(public, getOlderMsgs) {
if(typeof public !== 'boolean') throw('Need to know if public or faction chat.');
var b = map.getBounds();
@ -42,75 +42,213 @@ window.getPostDataForChat = function(public, getOlderMsgs) {
if(getOlderMsgs) {
// ask for older chat when scrolling up
data = $.extend(data, {maxTimestampMs: getOldestTimestampChat(public)});
data = $.extend(data, {maxTimestampMs: chat.getOldestTimestamp(public)});
} else {
// ask for newer chat
$.extend(data, {minTimestampMs: getNewestTimestampChat(public)});
var min = chat.getNewestTimestamp(public);
// the inital request will have both timestamp values set to -1,
// thus we receive the newest desiredNumItems. After that, we will
// only receive messages with a timestamp greater or equal to min
// above.
// After resuming from idle, there might be more new messages than
// desiredNumItems. So on the first request, we are not really up to
// date. We will eventually catch up, as long as there are less new
// messages than desiredNumItems per each refresh cycle.
// A proper solution would be to query until no more new results are
// returned. Another way would be to set desiredNumItems to a very
// large number so we really get all new messages since the last
// request. Setting desiredNumItems to -1 does unfortunately not
// work.
// Currently this edge case is not handled. Lets see if this is a
// problem in crowded areas.
$.extend(data, {minTimestampMs: min});
}
return data;
}
window.requestFactionChat = function(getOlderMsgs) {
if(window.idleTime >= MAX_IDLE_TIME) {
console.log('user has been idle for ' + idleTime + ' minutes. Skipping faction chat.');
renderUpdateStatus();
return;
}
//
// requesting faction
//
data = getPostDataForChat(false, false);
window.requests.add(window.postAjax('getPaginatedPlextsV2', data, window.handleFactionChat));
window.chat._requestOldFactionRunning = false;
window.chat.requestOldFaction = function(isRetry) {
if(chat._requestOldFactionRunning) return;
if(isIdle()) return renderUpdateStatus();
chat._requestOldFactionRunning = true;
var d = chat.genPostData(false, true);
var r = window.postAjax(
'getPaginatedPlextsV2',
d,
chat.handleOldFaction,
isRetry
? function() { window.chat._requestOldFactionRunning = false; }
: function() { window.chat.requestOldFaction(true) }
);
requests.add(r);
}
window.renderChatMsg = function(msg, nick, time, team) {
window.chat._requestNewFactionRunning = false;
window.chat.requestNewFaction = function(isRetry) {
if(chat._requestNewFactionRunning) return;
if(window.isIdle()) return renderUpdateStatus();
chat._requestNewFactionRunning = true;
var d = chat.genPostData(false, false);
var r = window.postAjax(
'getPaginatedPlextsV2',
d,
chat.handleNewFaction,
isRetry
? function() { window.chat._requestNewFactionRunning = false; }
: function() { window.chat.requestNewFaction(true) }
);
requests.add(r);
}
//
// handle faction
//
window.chat.handleOldFaction = function(data, textStatus, jqXHR) {
chat._requestOldFactionRunning = false;
chat.handleFaction(data, textStatus, jqXHR, true);
}
window.chat.handleNewFaction = function(data, textStatus, jqXHR) {
chat._requestNewFactionRunning = false;
chat.handleFaction(data, textStatus, jqXHR, false);
}
window.chat._displayedFactionGuids = [];
window.chat.handleFaction = function(data, textStatus, jqXHR, isOldMsgs) {
if(!data || !data.result) return console.warn('Couldnt get chat data. Pausing chat for now.');
var msgs = '';
var prevTime = null;
$.each(data.result.reverse(), function(ind, json) {
// avoid duplicates
if(window.chat._displayedFactionGuids.indexOf(json[0]) !== -1) return;
window.chat._displayedFactionGuids.push(json[0]);
var time = json[1];
var msg = json[2].plext.markup[2][1].plain;
var team = json[2].plext.team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
var nick = json[2].plext.markup[1][1].plain.slice(0, -2); // cut “: ” at end
var pguid = json[2].plext.markup[1][1].guid;
window.setPlayerName(pguid, nick); // free nick name resolves
var nowTime = new Date(time).toLocaleDateString();
if(prevTime && prevTime !== nowTime)
msgs += '<summary>'+nowTime+'</summary>';
msgs += chat.renderMsg(msg, nick, time, team);
prevTime = nowTime;
});
var c = $('#chatfaction');
var scrollBefore = scrollBottom(c);
if(isOldMsgs)
c.prepend(msgs);
else
c.append(msgs);
// If scrolled down completely, keep it that way so new messages can
// be seen easily. If scrolled up, only need to fix scroll position
// when old messages are added. New messages added at the bottom dont
// change the view and enabling this would make the chat scroll down
// for every added message, even if the user wants to read old stuff.
if(scrollBefore === 0 || isOldMsgs) {
c.data('ignoreNextScroll', true);
c.scrollTop(c.scrollTop() + (scrollBottom(c)-scrollBefore));
}
chat.needMoreMessages();
}
window.chat.toggle = function() {
var c = $('#chat, #chatcontrols');
if(c.hasClass('expand')) {
$('#chatcontrols a:first').text('expand');
c.removeClass('expand');
} else {
$('#chatcontrols a:first').text('shrink');
c.addClass('expand');
chat.needMoreMessages();
}
}
window.chat.request = function() {
console.log('refreshing chat');
chat.requestNewFaction();
//~ chat.requestNewPublic();
}
// checks if there are enough messages in the selected chat tab and
// loads more if not.
window.chat.needMoreMessages = function() {
var activeChat = $('#chat > :visible');
if(scrollBottom(activeChat) !== 0 || activeChat.scrollTop() !== 0) return;
console.log('no scrollbar in active chat, requesting more msgs');
if($('#chatcontrols a:last.active'))
chat.requestOldFaction();
else
chat.requestOldPublic();
}
window.chat.setupTime = function() {
var inputTime = $('#chatinput time');
var updateTime = function() {
if(window.isIdle()) return;
var d = new Date();
inputTime.text(d.toLocaleTimeString().slice(0, 5));
// update ON the minute (1ms after)
setTimeout(updateTime, (60 - d.getSeconds()) * 1000 + 1);
};
updateTime();
window.addResumeFunction(updateTime);
}
window.chat.setup = function() {
$('#chatcontrols, #chat, #chatinput').show();
$('#chatcontrols a:first').click(window.chat.toggle);
$('#chatinput').click(function() {
$('#chatinput input').focus();
});
window.chat.setupTime();
$('#chatfaction').scroll(function() {
var t = $(this);
if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
if(t.scrollTop() < 200) chat.requestOldFaction();
if(scrollBottom(t) === 0) chat.requestNewFaction();
});
$('#chatpublic, #chatbot').scroll(function() {
var t = $(this);
if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
if(t.scrollTop() < 200) chat.requestOldPublic();
if(scrollBottom(t) === 0) chat.requestNewPublic();
});
chat.requestNewFaction();
window.addResumeFunction(chat.request);
window.requests.addRefreshFunction(chat.request);
}
window.chat.renderMsg = function(msg, nick, time, team) {
var ta = unixTimeToHHmm(time);
var tb = unixTimeToString(time, true);
var t = '<time title="'+tb+'" data-timestamp="'+time+'">'+ta+'</time>';
var s = 'style="color:'+COLORS[team]+'"';
return '<p>'+t+'<mark '+s+'>'+nick+'</mark><span>'+msg+'</span></p>';
}
window.handleFactionChat = function(data, textStatus, jqXHR) {
var appMsg = '';
var first = null;
var last;
$.each(data.result.reverse(), function(ind, chat) {
var time = chat[1];
var msg = chat[2].plext.markup[2][1].plain;
var team = chat[2].plext.team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
var nick = chat[2].plext.markup[1][1].plain.slice(0, -2); // cut “: ” at end
var guid = chat[2].plext.markup[1][1].guid;
window.setPlayerName(guid, nick); // free nick name resolves
if(!first) first = time;
last = time;
appMsg += renderChatMsg(msg, nick, time, team);
});
$('#chatfaction').html(appMsg);
}
window.toggleChat = function() {
var c = $('#chat');
var cc = $('#chatcontrols');
if(c.data('toggle')) {
$('#chatcontrols a:first').text('expand');
c.css('height', CHAT_SHRINKED+'px');
c.css('top', 'auto');
c.data('toggle', false);
cc.css('top', 'auto');
cc.css('bottom', (CHAT_SHRINKED+4)+'px');
} else {
$('#chatcontrols a:first').text('shrink');
c.css('height', 'auto');
c.css('top', '25px');
c.data('toggle', true);
cc.css('top', '0');
cc.css('bottom', 'auto');
}
}
window.setupChat = function() {
$('#chatcontrols a').first().click(window.toggleChat);
requestFactionChat();
}

View File

@ -7,7 +7,9 @@ var idleReset = function (e) {
// update immediately when the user comes back
if(isIdle()) {
window.idleTime = 0;
window.requestData();
$.each(window._onResumeFunctions, function(ind, f) {
f();
});
}
window.idleTime = 0;
};
@ -16,3 +18,11 @@ $('body').mousemove(idleReset).keypress(idleReset);
window.isIdle = function() {
return window.idleTime >= MAX_IDLE_TIME;
}
window._onResumeFunctions = [];
// add your function here if you want to be notified when the user
// resumes from being idle
window.addResumeFunction = function(f) {
window._onResumeFunctions.push(f);
}

View File

@ -5,36 +5,9 @@
// action.
// sets the timer for the next auto refresh. Ensures only one timeout
// is queued. May be given 'override' in milliseconds if time should
// not be guessed automatically. Especially useful if a little delay
// is required, for example when zooming.
window.startRefreshTimeout = function(override) {
// may be required to remove 'paused during interaction' message in
// status bar
window.renderUpdateStatus();
if(refreshTimeout) clearTimeout(refreshTimeout);
if(override) {
console.log('refreshing in ' + override + 'ms');
refreshTimeout = setTimeout(window.requestData, override);
return;
}
var t = REFRESH*1000;
var adj = ZOOM_LEVEL_ADJ * (18 - window.map.getZoom());
if(adj > 0) t += adj*1000;
console.log("next auto refresh in " + t/1000 + " seconds.");
refreshTimeout = setTimeout(window.requestData, t);
}
// requests map data for current viewport. For details on how this
// works, refer to the description in “MAP DATA REQUEST CALCULATORS”
window.requestData = function() {
if(window.idleTime >= MAX_IDLE_TIME) {
console.log('user has been idle for ' + idleTime + ' minutes. Skipping refresh.');
renderUpdateStatus();
return;
}
console.log('refreshing data');
requests.abort();
cleanUp();
@ -81,7 +54,6 @@ window.requestData = function() {
// works on map data response and ensures entities are drawn/updated.
window.handleDataResponse = function(data, textStatus, jqXHR) {
// remove from active ajax queries list
window.requests.remove(jqXHR);
if(!data || !data.result) {
console.warn(data);
return;
@ -158,7 +130,12 @@ window.cleanUp = function() {
// removes given entity from map
window.removeByGuid = function(guid) {
// portals end in “.11” or “.12“, links in “.9", fields in “.b”
// .11 == portals
// .12 == portals
// .9 == links
// .b == fields
// .c == player/creator
// .d == chat messages
switch(guid.slice(33)) {
case '11':
case '12':
@ -240,7 +217,7 @@ window.renderLink = function(ent) {
poly.on('remove', function() { delete window.links[this.options.guid]; });
poly.on('add', function() { window.links[this.options.guid] = this; });
poly.addTo(linksLayer);
poly.addTo(linksLayer).bringToBack();
}
// renders a field on the map from a given entity
@ -264,5 +241,5 @@ window.renderField = function(ent) {
poly.on('remove', function() { delete window.fields[this.options.guid]; });
poly.on('add', function() { window.fields[this.options.guid] = this; });
poly.addTo(fieldsLayer);
poly.addTo(fieldsLayer).bringToBack();
}

View File

@ -25,7 +25,6 @@ window.requests.abort = function() {
window.activeRequests = [];
window.failedRequestCount = 0;
startRefreshTimeout();
renderUpdateStatus();
}
@ -56,3 +55,49 @@ window.renderUpdateStatus = function() {
$('#updatestatus').html(t);
}
// sets the timer for the next auto refresh. Ensures only one timeout
// is queued. May be given 'override' in milliseconds if time should
// not be guessed automatically. Especially useful if a little delay
// is required, for example when zooming.
window.startRefreshTimeout = function(override) {
// may be required to remove 'paused during interaction' message in
// status bar
window.renderUpdateStatus();
if(refreshTimeout) clearTimeout(refreshTimeout);
var t = 0;
if(override) {
t = override;
} else {
t = REFRESH*1000;
var adj = ZOOM_LEVEL_ADJ * (18 - window.map.getZoom());
if(adj > 0) t += adj*1000;
}
var next = new Date(new Date().getTime() + t).toLocaleTimeString();
console.log('planned refresh: ' + next);
refreshTimeout = setTimeout(window.requests._callOnRefreshFunctions, t);
}
window.requests._onRefreshFunctions = [];
window.requests._callOnRefreshFunctions = function() {
startRefreshTimeout();
if(isIdle()) {
console.log('user has been idle for ' + idleTime + ' minutes. Skipping refresh.');
renderUpdateStatus();
return;
}
console.log('refreshing');
$.each(window.requests._onRefreshFunctions, function(ind, f) {
f();
});
}
// add method here to be notified of auto-refreshes
window.requests.addRefreshFunction = function(f) {
window.requests._onRefreshFunctions.push(f);
}

View File

@ -48,13 +48,14 @@ window.digits = function(d) {
// error: see above. Additionally it is logged if the request failed.
window.postAjax = function(action, data, success, error) {
data = JSON.stringify($.extend({method: 'dashboard.'+action}, data));
var remove = function(data, textStatus, jqXHR) { window.requests.remove(jqXHR); };
var errCnt = function(jqXHR) { window.failedRequestCount++; window.requests.remove(jqXHR); };
return $.ajax({
url: 'rpc/dashboard.'+action,
type: 'POST',
data: data,
dataType: 'json',
success: success,
success: [remove, success],
error: error ? [errCnt, error] : errCnt,
contentType: 'application/json; charset=utf-8',
beforeSend: function(req) {
@ -110,3 +111,11 @@ window.getPaddedBounds = function() {
window._storedPaddedBounds = p;
return p;
}
// returns number of pixels left to scroll down before reaching the
// bottom. Works similar to the native scrollTop function.
window.scrollBottom = function(elm) {
if(typeof elm === 'string') elm = $(elm);
return elm.get(0).scrollHeight - elm.innerHeight() - elm.scrollTop();
}

View File

@ -0,0 +1,13 @@
// http://www.ingress.com/rpc/dashboard.sendPlext
// posting:
{"message":"x","latE6":73689520,"lngE6":95322756,"factionOnly":true,"method":"dashboard.sendPlext"}
{"message":"x","latE6":73689520,"lngE6":95322756,"method":"dashboard.sendPlext"}
// faction response:
{"gameBasket": {"deletedEntityGuids": [], "gameEntities": [], "inventory": []}, "result": "c709d0aa51664510a43c635d5df919cf.d"}
// public response:
{"gameBasket": {"deletedEntityGuids": [], "gameEntities": [], "inventory": []}, "result": "1b113466f403499191eae568915b7cc9.d"}

View File

@ -46,14 +46,15 @@ document.getElementsByTagName('head')[0].innerHTML = ''
document.getElementsByTagName('body')[0].innerHTML = ''
+ '<div id="map">Loading, please wait</div>'
+ '<div id="chatcontrols">'
+ '<div id="chatcontrols" style="display:none">'
+ ' <a>expand</a><a>automated</a><a>public</a><a class="active">faction</a>'
+ '</div>'
+ '<div id="chat">'
+ '<div id="chat" style="display:none">'
+ ' <div id="chatfaction"></div>'
+ ' <div id="chatpublic"></div>'
+ ' <div id="chatbot"></div>'
+ '</div>'
+ '<form id="chatinput" style="display:none"><time></time><span>tell faction:</span><input type="text"/></form>'
+ '<div id="sidebar" style="display: none">'
+ ' <div id="playerstat">t</div>'
+ ' <div id="gamestat">&nbsp;loading global control stats</div>'

View File

@ -155,6 +155,17 @@ mark {
background: transparent;
}
summary {
color: #bbb;
display: inline-block;
font-family: Verdana,sans-serif;
height: 16px;
overflow: hidden;
padding: 0 2px;
white-space: nowrap;
width: 683px;
}
#chat span {
display: inline-block;
width: 546px;

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @id ingress-intel-total-conversion@breunigs
// @name intel map total conversion
// @version 0.1-2013-02-02-003549
// @version 0.1-2013-02-03-131520
// @namespace https://github.com/breunigs/ingress-intel-total-conversion
// @updateURL https://raw.github.com/breunigs/ingress-intel-total-conversion/gh-pages/total-conversion-build.user.js
// @downloadURL https://raw.github.com/breunigs/ingress-intel-total-conversion/gh-pages/total-conversion-build.user.js
@ -46,14 +46,15 @@ document.getElementsByTagName('head')[0].innerHTML = ''
document.getElementsByTagName('body')[0].innerHTML = ''
+ '<div id="map">Loading, please wait</div>'
+ '<div id="chatcontrols">'
+ '<div id="chatcontrols" style="display:none">'
+ ' <a>expand</a><a>automated</a><a>public</a><a class="active">faction</a>'
+ '</div>'
+ '<div id="chat">'
+ '<div id="chat" style="display:none">'
+ ' <div id="chatfaction"></div>'
+ ' <div id="chatpublic"></div>'
+ ' <div id="chatbot"></div>'
+ '</div>'
+ '<form id="chatinput" style="display:none"><time></time><span>tell faction:</span><input type="text"/></form>'
+ '<div id="sidebar" style="display: none">'
+ ' <div id="playerstat">t</div>'
+ ' <div id="gamestat">&nbsp;loading global control stats</div>'
@ -150,36 +151,9 @@ window.fields = {};
// action.
// sets the timer for the next auto refresh. Ensures only one timeout
// is queued. May be given 'override' in milliseconds if time should
// not be guessed automatically. Especially useful if a little delay
// is required, for example when zooming.
window.startRefreshTimeout = function(override) {
// may be required to remove 'paused during interaction' message in
// status bar
window.renderUpdateStatus();
if(refreshTimeout) clearTimeout(refreshTimeout);
if(override) {
console.log('refreshing in ' + override + 'ms');
refreshTimeout = setTimeout(window.requestData, override);
return;
}
var t = REFRESH*1000;
var adj = ZOOM_LEVEL_ADJ * (18 - window.map.getZoom());
if(adj > 0) t += adj*1000;
console.log("next auto refresh in " + t/1000 + " seconds.");
refreshTimeout = setTimeout(window.requestData, t);
}
// requests map data for current viewport. For details on how this
// works, refer to the description in “MAP DATA REQUEST CALCULATORS”
window.requestData = function() {
if(window.idleTime >= MAX_IDLE_TIME) {
console.log('user has been idle for ' + idleTime + ' minutes. Skipping refresh.');
renderUpdateStatus();
return;
}
console.log('refreshing data');
requests.abort();
cleanUp();
@ -226,7 +200,6 @@ window.requestData = function() {
// works on map data response and ensures entities are drawn/updated.
window.handleDataResponse = function(data, textStatus, jqXHR) {
// remove from active ajax queries list
window.requests.remove(jqXHR);
if(!data || !data.result) {
console.warn(data);
return;
@ -303,7 +276,12 @@ window.cleanUp = function() {
// removes given entity from map
window.removeByGuid = function(guid) {
// portals end in “.11” or “.12“, links in “.9", fields in “.b”
// .11 == portals
// .12 == portals
// .9 == links
// .b == fields
// .c == player/creator
// .d == chat messages
switch(guid.slice(33)) {
case '11':
case '12':
@ -385,7 +363,7 @@ window.renderLink = function(ent) {
poly.on('remove', function() { delete window.links[this.options.guid]; });
poly.on('add', function() { window.links[this.options.guid] = this; });
poly.addTo(linksLayer);
poly.addTo(linksLayer).bringToBack();
}
// renders a field on the map from a given entity
@ -409,7 +387,7 @@ window.renderField = function(ent) {
poly.on('remove', function() { delete window.fields[this.options.guid]; });
poly.on('add', function() { window.fields[this.options.guid] = this; });
poly.addTo(fieldsLayer);
poly.addTo(fieldsLayer).bringToBack();
}
@ -440,7 +418,6 @@ window.requests.abort = function() {
window.activeRequests = [];
window.failedRequestCount = 0;
startRefreshTimeout();
renderUpdateStatus();
}
@ -473,6 +450,52 @@ window.renderUpdateStatus = function() {
}
// sets the timer for the next auto refresh. Ensures only one timeout
// is queued. May be given 'override' in milliseconds if time should
// not be guessed automatically. Especially useful if a little delay
// is required, for example when zooming.
window.startRefreshTimeout = function(override) {
// may be required to remove 'paused during interaction' message in
// status bar
window.renderUpdateStatus();
if(refreshTimeout) clearTimeout(refreshTimeout);
var t = 0;
if(override) {
t = override;
} else {
t = REFRESH*1000;
var adj = ZOOM_LEVEL_ADJ * (18 - window.map.getZoom());
if(adj > 0) t += adj*1000;
}
var next = new Date(new Date().getTime() + t).toLocaleTimeString();
console.log('planned refresh: ' + next);
refreshTimeout = setTimeout(window.requests._callOnRefreshFunctions, t);
}
window.requests._onRefreshFunctions = [];
window.requests._callOnRefreshFunctions = function() {
startRefreshTimeout();
if(isIdle()) {
console.log('user has been idle for ' + idleTime + ' minutes. Skipping refresh.');
renderUpdateStatus();
return;
}
console.log('refreshing');
$.each(window.requests._onRefreshFunctions, function(ind, f) {
f();
});
}
// add method here to be notified of auto-refreshes
window.requests.addRefreshFunction = function(f) {
window.requests._onRefreshFunctions.push(f);
}
// UTILS + MISC ///////////////////////////////////////////////////////
@ -523,13 +546,14 @@ window.digits = function(d) {
// error: see above. Additionally it is logged if the request failed.
window.postAjax = function(action, data, success, error) {
data = JSON.stringify($.extend({method: 'dashboard.'+action}, data));
var remove = function(data, textStatus, jqXHR) { window.requests.remove(jqXHR); };
var errCnt = function(jqXHR) { window.failedRequestCount++; window.requests.remove(jqXHR); };
return $.ajax({
url: 'rpc/dashboard.'+action,
type: 'POST',
data: data,
dataType: 'json',
success: success,
success: [remove, success],
error: error ? [errCnt, error] : errCnt,
contentType: 'application/json; charset=utf-8',
beforeSend: function(req) {
@ -587,6 +611,14 @@ window.getPaddedBounds = function() {
}
// returns number of pixels left to scroll down before reaching the
// bottom. Works similar to the native scrollTop function.
window.scrollBottom = function(elm) {
if(typeof elm === 'string') elm = $(elm);
return elm.get(0).scrollHeight - elm.innerHeight() - elm.scrollTop();
}
// SETUP /////////////////////////////////////////////////////////////
@ -613,12 +645,13 @@ window.setupLargeImagePreview = function() {
});
}
window.setupStyles = function() {
$('head').append('<style>' +
[ '#largepreview.res img { border:2px solid '+COLORS[TEAM_RES]+'; } ',
'#largepreview.enl img { border:2px solid '+COLORS[TEAM_ENL]+'; } ',
'#largepreview.none img { border:2px solid '+COLORS[TEAM_NONE]+'; } ',
'#chatcontrols { bottom: '+(CHAT_SHRINKED+4)+'px; }',
'#chatcontrols { bottom: '+(CHAT_SHRINKED+24)+'px; }',
'#chat { height: '+CHAT_SHRINKED+'px; } ',
'#updatestatus { width:'+(SIDEBAR_WIDTH-2*4)+'px; } ',
'#sidebar, #gamestat, #gamestat span, input, ',
@ -671,21 +704,19 @@ window.setupMap = function() {
});
// map update status handling
map.on('zoomstart', function() { window.mapRunsUserAction = true });
map.on('movestart', function() { window.mapRunsUserAction = true });
map.on('zoomend', function() { window.mapRunsUserAction = false });
map.on('moveend', function() { window.mapRunsUserAction = false });
map.on('movestart zoomstart', function() { window.mapRunsUserAction = true });
map.on('moveend zoomend', function() { window.mapRunsUserAction = false });
// update map hooks
map.on('zoomstart', window.requests.abort);
map.on('zoomend', function() { window.startRefreshTimeout(500) });
map.on('movestart', window.requests.abort );
map.on('moveend', function() { window.startRefreshTimeout(500) });
map.on('movestart zoomstart', window.requests.abort);
map.on('moveend zoomend', function() { window.startRefreshTimeout(500) });
// run once on init
window.requestData();
window.startRefreshTimeout();
window.addResumeFunction(window.requestData);
window.requests.addRefreshFunction(window.requestData);
};
// renders player details into the website. Since the player info is
@ -740,7 +771,7 @@ function boot() {
window.setupLargeImagePreview();
window.updateGameScore();
window.setupPlayerStat();
window.setupChat();
window.chat.setup();
// read here ONCE, so the URL is only evaluated one time after the
// necessary data has been loaded.
urlPortal = getURLParam('pguid');
@ -765,9 +796,9 @@ var LEAFLET = 'http://cdn.leafletjs.com/leaflet-0.5/leaflet.js';
load(JQUERY, LEAFLET).then(LLGMAPS).thenRun(boot);
window.chat = function() {}
window.chat = function() {};
window.getOldestTimestampChat = function(public) {
window.chat.getOldestTimestamp = function(public) {
if(public) {
var a = $('#chatpublic time:first').data('timestamp');
var b = $('#chatbot time:first').data('timestamp');
@ -778,7 +809,7 @@ window.getOldestTimestampChat = function(public) {
}
}
window.getNewestTimestampChat = function(public) {
window.chat.getNewestTimestamp = function(public) {
if(public) {
var a = $('#chatpublic time:last').data('timestamp');
var b = $('#chatbot time:last').data('timestamp');
@ -789,7 +820,7 @@ window.getNewestTimestampChat = function(public) {
}
}
window.getPostDataForChat = function(public, getOlderMsgs) {
window.chat.genPostData = function(public, getOlderMsgs) {
if(typeof public !== 'boolean') throw('Need to know if public or faction chat.');
var b = map.getBounds();
@ -809,26 +840,210 @@ window.getPostDataForChat = function(public, getOlderMsgs) {
if(getOlderMsgs) {
// ask for older chat when scrolling up
data = $.extend(data, {maxTimestampMs: getOldestTimestampChat(public)});
data = $.extend(data, {maxTimestampMs: chat.getOldestTimestamp(public)});
} else {
// ask for newer chat
$.extend(data, {minTimestampMs: getNewestTimestampChat(public)});
var min = chat.getNewestTimestamp(public);
// the inital request will have both timestamp values set to -1,
// thus we receive the newest desiredNumItems. After that, we will
// only receive messages with a timestamp greater or equal to min
// above.
// After resuming from idle, there might be more new messages than
// desiredNumItems. So on the first request, we are not really up to
// date. We will eventually catch up, as long as there are less new
// messages than desiredNumItems per each refresh cycle.
// A proper solution would be to query until no more new results are
// returned. Another way would be to set desiredNumItems to a very
// large number so we really get all new messages since the last
// request. Setting desiredNumItems to -1 does unfortunately not
// work.
// Currently this edge case is not handled. Lets see if this is a
// problem in crowded areas.
$.extend(data, {minTimestampMs: min});
}
return data;
}
window.requestFactionChat = function(getOlderMsgs) {
if(window.idleTime >= MAX_IDLE_TIME) {
console.log('user has been idle for ' + idleTime + ' minutes. Skipping faction chat.');
renderUpdateStatus();
return;
}
//
// requesting faction
//
data = getPostDataForChat(false, false);
window.requests.add(window.postAjax('getPaginatedPlextsV2', data, window.handleFactionChat));
window.chat._requestOldFactionRunning = false;
window.chat.requestOldFaction = function(isRetry) {
if(chat._requestOldFactionRunning) return;
if(isIdle()) return renderUpdateStatus();
chat._requestOldFactionRunning = true;
var d = chat.genPostData(false, true);
var r = window.postAjax(
'getPaginatedPlextsV2',
d,
chat.handleOldFaction,
isRetry
? function() { window.chat._requestOldFactionRunning = false; }
: function() { window.chat.requestOldFaction(true) }
);
requests.add(r);
}
window.renderChatMsg = function(msg, nick, time, team) {
window.chat._requestNewFactionRunning = false;
window.chat.requestNewFaction = function(isRetry) {
if(chat._requestNewFactionRunning) return;
if(window.isIdle()) return renderUpdateStatus();
chat._requestNewFactionRunning = true;
var d = chat.genPostData(false, false);
var r = window.postAjax(
'getPaginatedPlextsV2',
d,
chat.handleNewFaction,
isRetry
? function() { window.chat._requestNewFactionRunning = false; }
: function() { window.chat.requestNewFaction(true) }
);
requests.add(r);
}
//
// handle faction
//
window.chat.handleOldFaction = function(data, textStatus, jqXHR) {
chat._requestOldFactionRunning = false;
chat.handleFaction(data, textStatus, jqXHR, true);
}
window.chat.handleNewFaction = function(data, textStatus, jqXHR) {
chat._requestNewFactionRunning = false;
chat.handleFaction(data, textStatus, jqXHR, false);
}
window.chat._displayedFactionGuids = [];
window.chat.handleFaction = function(data, textStatus, jqXHR, isOldMsgs) {
if(!data || !data.result) return console.warn('Couldnt get chat data. Pausing chat for now.');
var msgs = '';
var prevTime = null;
$.each(data.result.reverse(), function(ind, json) {
// avoid duplicates
if(window.chat._displayedFactionGuids.indexOf(json[0]) !== -1) return;
window.chat._displayedFactionGuids.push(json[0]);
var time = json[1];
var msg = json[2].plext.markup[2][1].plain;
var team = json[2].plext.team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
var nick = json[2].plext.markup[1][1].plain.slice(0, -2); // cut “: ” at end
var pguid = json[2].plext.markup[1][1].guid;
window.setPlayerName(pguid, nick); // free nick name resolves
var nowTime = new Date(time).toLocaleDateString();
if(prevTime && prevTime !== nowTime)
msgs += '<summary>'+nowTime+'</summary>';
msgs += chat.renderMsg(msg, nick, time, team);
prevTime = nowTime;
});
var c = $('#chatfaction');
var scrollBefore = scrollBottom(c);
if(isOldMsgs)
c.prepend(msgs);
else
c.append(msgs);
// If scrolled down completely, keep it that way so new messages can
// be seen easily. If scrolled up, only need to fix scroll position
// when old messages are added. New messages added at the bottom dont
// change the view and enabling this would make the chat scroll down
// for every added message, even if the user wants to read old stuff.
if(scrollBefore === 0 || isOldMsgs) {
c.data('ignoreNextScroll', true);
c.scrollTop(c.scrollTop() + (scrollBottom(c)-scrollBefore));
}
chat.needMoreMessages();
}
window.chat.toggle = function() {
var c = $('#chat, #chatcontrols');
if(c.hasClass('expand')) {
$('#chatcontrols a:first').text('expand');
c.removeClass('expand');
} else {
$('#chatcontrols a:first').text('shrink');
c.addClass('expand');
chat.needMoreMessages();
}
}
window.chat.request = function() {
console.log('refreshing chat');
chat.requestNewFaction();
//~ chat.requestNewPublic();
}
// checks if there are enough messages in the selected chat tab and
// loads more if not.
window.chat.needMoreMessages = function() {
var activeChat = $('#chat > :visible');
if(scrollBottom(activeChat) !== 0 || activeChat.scrollTop() !== 0) return;
console.log('no scrollbar in active chat, requesting more msgs');
if($('#chatcontrols a:last.active'))
chat.requestOldFaction();
else
chat.requestOldPublic();
}
window.chat.setupTime = function() {
var inputTime = $('#chatinput time');
var updateTime = function() {
if(window.isIdle()) return;
var d = new Date();
inputTime.text(d.toLocaleTimeString().slice(0, 5));
// update ON the minute (1ms after)
setTimeout(updateTime, (60 - d.getSeconds()) * 1000 + 1);
};
updateTime();
window.addResumeFunction(updateTime);
}
window.chat.setup = function() {
$('#chatcontrols, #chat, #chatinput').show();
$('#chatcontrols a:first').click(window.chat.toggle);
$('#chatinput').click(function() {
$('#chatinput input').focus();
});
window.chat.setupTime();
$('#chatfaction').scroll(function() {
var t = $(this);
if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
if(t.scrollTop() < 200) chat.requestOldFaction();
if(scrollBottom(t) === 0) chat.requestNewFaction();
});
$('#chatpublic, #chatbot').scroll(function() {
var t = $(this);
if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
if(t.scrollTop() < 200) chat.requestOldPublic();
if(scrollBottom(t) === 0) chat.requestNewPublic();
});
chat.requestNewFaction();
window.addResumeFunction(chat.request);
window.requests.addRefreshFunction(chat.request);
}
window.chat.renderMsg = function(msg, nick, time, team) {
var ta = unixTimeToHHmm(time);
var tb = unixTimeToString(time, true);
var t = '<time title="'+tb+'" data-timestamp="'+time+'">'+ta+'</time>';
@ -836,52 +1051,6 @@ window.renderChatMsg = function(msg, nick, time, team) {
return '<p>'+t+'<mark '+s+'>'+nick+'</mark><span>'+msg+'</span></p>';
}
window.handleFactionChat = function(data, textStatus, jqXHR) {
var appMsg = '';
var first = null;
var last;
$.each(data.result.reverse(), function(ind, chat) {
var time = chat[1];
var msg = chat[2].plext.markup[2][1].plain;
var team = chat[2].plext.team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
var nick = chat[2].plext.markup[1][1].plain.slice(0, -2); // cut “: ” at end
var guid = chat[2].plext.markup[1][1].guid;
window.setPlayerName(guid, nick); // free nick name resolves
if(!first) first = time;
last = time;
appMsg += renderChatMsg(msg, nick, time, team);
});
$('#chatfaction').html(appMsg);
}
window.toggleChat = function() {
var c = $('#chat');
var cc = $('#chatcontrols');
if(c.data('toggle')) {
$('#chatcontrols a:first').text('expand');
c.css('height', CHAT_SHRINKED+'px');
c.css('top', 'auto');
c.data('toggle', false);
cc.css('top', 'auto');
cc.css('bottom', (CHAT_SHRINKED+4)+'px');
} else {
$('#chatcontrols a:first').text('shrink');
c.css('height', 'auto');
c.css('top', '25px');
c.data('toggle', true);
cc.css('top', '0');
cc.css('bottom', 'auto');
}
}
window.setupChat = function() {
$('#chatcontrols a').first().click(window.toggleChat);
requestFactionChat();
}
// PORTAL DETAILS DISPLAY ////////////////////////////////////////////
@ -1131,7 +1300,9 @@ var idleReset = function (e) {
// update immediately when the user comes back
if(isIdle()) {
window.idleTime = 0;
window.requestData();
$.each(window._onResumeFunctions, function(ind, f) {
f();
});
}
window.idleTime = 0;
};
@ -1141,6 +1312,14 @@ window.isIdle = function() {
return window.idleTime >= MAX_IDLE_TIME;
}
window._onResumeFunctions = [];
// add your function here if you want to be notified when the user
// resumes from being idle
window.addResumeFunction = function(f) {
window._onResumeFunctions.push(f);
}
// LOCATION HANDLING /////////////////////////////////////////////////