Skip to content

Commit 1fcbd28

Browse files
authored
Merge pull request #768 from modos189/feature/extend-player-tracker-plugins
Player activity tracker: refactoring
2 parents e4f2084 + 3afb6c8 commit 1fcbd28

File tree

4 files changed

+116
-63
lines changed

4 files changed

+116
-63
lines changed

core/code/utils.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,42 @@ const formatDistance = (distance) => {
224224
return `${IITC.utils.formatNumber(value)}${unit}`;
225225
};
226226

227+
/**
228+
* Formats the time difference between two timestamps (in milliseconds) as a string.
229+
*
230+
* @memberof IITC.utils
231+
* @function formatAgo
232+
* @param {number} time - The past timestamp in milliseconds.
233+
* @param {number} now - The current timestamp in milliseconds.
234+
* @param {Object} [options] - Options for formatting.
235+
* @param {boolean} [options.showSeconds=false] - Whether to include seconds in the result.
236+
* @returns {string} The formatted time difference (e.g., "45s", "5m", "2h 45m", "1d 3h 45m")
237+
*/
238+
const formatAgo = (time, now, options = { showSeconds: false }) => {
239+
const secondsTotal = Math.floor(Math.max(0, (now - time) / 1000));
240+
241+
// Calculate time units
242+
const days = Math.floor(secondsTotal / 86400);
243+
const hours = Math.floor((secondsTotal % 86400) / 3600);
244+
const minutes = Math.floor((secondsTotal % 3600) / 60);
245+
const seconds = secondsTotal % 60;
246+
247+
const result = [];
248+
249+
// Include units conditionally based on non-zero values
250+
if (days > 0) result.push(`${days}d`);
251+
if (hours > 0 || result.length !== 0) result.push(`${hours}h`);
252+
if (minutes > 0 || result.length !== 0) result.push(`${minutes}m`);
253+
if (options.showSeconds && (result.length === 0 || seconds > 0)) result.push(`${seconds}s`);
254+
255+
// If no units were added, show "0" with the smallest available unit
256+
if (result.length === 0) {
257+
return options.showSeconds ? '0s' : '0m';
258+
}
259+
260+
return result.join(' ');
261+
};
262+
227263
/**
228264
* Checks if the device is a touch-enabled device.
229265
* Alias for `L.Browser.touch()`
@@ -448,6 +484,7 @@ IITC.utils = {
448484
unixTimeToHHmm,
449485
formatInterval,
450486
formatDistance,
487+
formatAgo,
451488
isTouchDevice,
452489
scrollBottom,
453490
escapeJS,

plugins/machina-tracker.js

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
// @name Machina tracker
22
// @author McBen
33
// @category Layer
4-
// @version 1.0.1
4+
// @version 1.1.0
55
// @description Show locations of Machina activities
66

77
/* exported setup, changelog --eslint */
8-
/* global L */
8+
/* global IITC, L */
99

1010
var changelog = [
11+
{
12+
version: '1.1.0',
13+
changes: ['Using `IITC.utils.formatAgo` instead of the plugin own function'],
14+
},
1115
{
1216
version: '1.0.1',
1317
changes: ['Version upgrade due to a change in the wrapper: plugin icons are now vectorized'],
@@ -146,17 +150,6 @@ machinaTracker.processNewData = function (data) {
146150
});
147151
};
148152

149-
machinaTracker.ago = function (time, now) {
150-
var s = (now - time) / 1000;
151-
var h = Math.floor(s / 3600);
152-
var m = Math.floor((s % 3600) / 60);
153-
var returnVal = m + 'm';
154-
if (h > 0) {
155-
returnVal = h + 'h' + returnVal;
156-
}
157-
return returnVal + ' ago';
158-
};
159-
160153
machinaTracker.createPortalLink = function (portal) {
161154
return $('<a>')
162155
.addClass('text-overflow-ellipsis')
@@ -188,7 +181,7 @@ machinaTracker.drawData = function () {
188181
var ageBucket = Math.min((now - event.time) / split, 3);
189182
var position = event.from.latLng;
190183

191-
var title = isTouchDev ? '' : machinaTracker.ago(event.time, now);
184+
var title = isTouchDev ? '' : IITC.utils.formatAgo(event.time, now) + ' ago';
192185
var icon = machinaTracker.icon;
193186
var opacity = 1 - 0.2 * ageBucket;
194187

@@ -199,7 +192,11 @@ machinaTracker.drawData = function () {
199192
linkList.appendTo(popup);
200193

201194
event.to.forEach((to) => {
202-
$('<li>').append(machinaTracker.createPortalLink(to)).append(' ').append(machinaTracker.ago(to.time, now)).appendTo(linkList);
195+
$('<li>')
196+
.append(machinaTracker.createPortalLink(to))
197+
.append(' ')
198+
.append(IITC.utils.formatAgo(to.time, now) + ' ago')
199+
.appendTo(linkList);
203200
});
204201

205202
var m = L.marker(position, { icon: icon, opacity: opacity, desc: popup[0], title: title });

plugins/player-activity-tracker.js

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
// @author breunigs
22
// @name Player activity tracker
33
// @category Layer
4-
// @version 0.13.2
4+
// @version 0.14.0
55
// @description Draw trails for the path a user took onto the map based on status messages in COMMs. Uses up to three hours of data. Does not request chat data on its own, even if that would be useful.
66

77
/* exported setup, changelog --eslint */
8-
/* global L -- eslint */
8+
/* global IITC, L -- eslint */
99

1010
var changelog = [
11+
{
12+
version: '0.14.0',
13+
changes: ['Using `IITC.utils.formatAgo` instead of the plugin own function', 'Refactoring to make it easier to extend plugin functions'],
14+
},
1115
{
1216
version: '0.13.2',
1317
changes: ['Refactoring: fix eslint'],
@@ -34,6 +38,7 @@ window.PLAYER_TRACKER_MAX_TIME = 3 * 60 * 60 * 1000; // in milliseconds
3438
window.PLAYER_TRACKER_MIN_ZOOM = 9;
3539
window.PLAYER_TRACKER_MIN_OPACITY = 0.3;
3640
window.PLAYER_TRACKER_LINE_COLOUR = '#FF00FD';
41+
window.PLAYER_TRACKER_MAX_DISPLAY_EVENTS = 10; // Maximum number of events in a popup
3742

3843
// use own namespace for plugin
3944
window.plugin.playerTracker = function () {};
@@ -274,24 +279,12 @@ window.plugin.playerTracker.getLatLngFromEvent = function (ev) {
274279
return L.latLng(lats / ev.latlngs.length, lngs / ev.latlngs.length);
275280
};
276281

277-
window.plugin.playerTracker.ago = function (time, now) {
278-
var s = (now - time) / 1000;
279-
var h = Math.floor(s / 3600);
280-
var m = Math.floor((s % 3600) / 60);
281-
var returnVal = m + 'm';
282-
if (h > 0) {
283-
returnVal = h + 'h' + returnVal;
284-
}
285-
return returnVal;
286-
};
287-
288282
window.plugin.playerTracker.drawData = function () {
289283
var isTouchDev = window.isTouchDevice();
290284

291285
var gllfe = window.plugin.playerTracker.getLatLngFromEvent;
292286

293-
var polyLineByAgeEnl = [[], [], [], []];
294-
var polyLineByAgeRes = [[], [], [], []];
287+
var polyLineByPlayerAndAge = {};
295288

296289
var split = window.PLAYER_TRACKER_MAX_TIME / 4;
297290
var now = Date.now();
@@ -308,13 +301,15 @@ window.plugin.playerTracker.drawData = function () {
308301
var ageBucket = Math.min(Math.trunc((now - p.time) / split), 4 - 1);
309302
var line = [gllfe(p), gllfe(playerData.events[i - 1])];
310303

311-
if (playerData.team === 'RESISTANCE') polyLineByAgeRes[ageBucket].push(line);
312-
else polyLineByAgeEnl[ageBucket].push(line);
304+
if (!polyLineByPlayerAndAge[plrname]) {
305+
polyLineByPlayerAndAge[plrname] = [[], [], [], []];
306+
}
307+
polyLineByPlayerAndAge[plrname][ageBucket].push(line);
313308
}
314309

315310
var evtsLength = playerData.events.length;
316311
var last = playerData.events[evtsLength - 1];
317-
var ago = window.plugin.playerTracker.ago;
312+
const ago = IITC.utils.formatAgo;
318313

319314
// tooltip for marker - no HTML - and not shown on touchscreen devices
320315
var tooltip = isTouchDev ? '' : plrname + ', ' + ago(last.time, now) + ' ago';
@@ -358,7 +353,7 @@ window.plugin.playerTracker.drawData = function () {
358353
popup.append('<br>').append('<br>').append(document.createTextNode('previous locations:')).append('<br>');
359354

360355
var table = $('<table>').appendTo(popup).css('border-spacing', '0');
361-
for (let i = evtsLength - 2; i >= 0 && i >= evtsLength - 10; i--) {
356+
for (let i = evtsLength - 2; i >= 0 && i >= evtsLength - window.PLAYER_TRACKER_MAX_DISPLAY_EVENTS; i--) {
362357
var ev = playerData.events[i];
363358
$('<tr>')
364359
.append($('<td>').text(ago(ev.time, now) + ' ago'))
@@ -375,7 +370,8 @@ window.plugin.playerTracker.drawData = function () {
375370
var icon = playerData.team === 'RESISTANCE' ? new window.plugin.playerTracker.iconRes() : new window.plugin.playerTracker.iconEnl();
376371
// as per OverlappingMarkerSpiderfier docs, click events (popups, etc) must be handled via it rather than the standard
377372
// marker click events. so store the popup text in the options, then display it in the oms click handler
378-
var m = L.marker(gllfe(last), { icon: icon, opacity: absOpacity, desc: popup[0], title: tooltip });
373+
const markerPos = gllfe(last);
374+
var m = L.marker(markerPos, { icon: icon, opacity: absOpacity, desc: popup[0], title: tooltip });
379375
m.addEventListener('spiderfiedclick', window.plugin.playerTracker.onClickListener);
380376

381377
// m.bindPopup(title);
@@ -389,7 +385,7 @@ window.plugin.playerTracker.drawData = function () {
389385

390386
playerData.marker = m;
391387

392-
m.addTo(playerData.team === 'RESISTANCE' ? window.plugin.playerTracker.drawnTracesRes : window.plugin.playerTracker.drawnTracesEnl);
388+
m.addTo(window.plugin.playerTracker.getDrawnTracesByTeam(playerData.team));
393389
window.registerMarkerForOMS(m);
394390

395391
// jQueryUI doesn’t automatically notice the new markers
@@ -399,36 +395,27 @@ window.plugin.playerTracker.drawData = function () {
399395
});
400396

401397
// draw the poly lines to the map
402-
$.each(polyLineByAgeEnl, function (i, polyLine) {
403-
if (polyLine.length === 0) return true;
404-
405-
var opts = {
406-
weight: 2 - 0.25 * i,
407-
color: window.PLAYER_TRACKER_LINE_COLOUR,
408-
interactive: false,
409-
opacity: 1 - 0.2 * i,
410-
dashArray: '5,8',
411-
};
398+
for (const [playerName, polyLineByAge] of Object.entries(polyLineByPlayerAndAge)) {
399+
polyLineByAge.forEach((polyLine, i) => {
400+
if (polyLine.length === 0) return;
401+
402+
const opts = {
403+
weight: 2 - 0.25 * i,
404+
color: window.PLAYER_TRACKER_LINE_COLOUR,
405+
interactive: false,
406+
opacity: 1 - 0.2 * i,
407+
dashArray: '5,8',
408+
};
412409

413-
$.each(polyLine, function (ind, poly) {
414-
L.polyline(poly, opts).addTo(window.plugin.playerTracker.drawnTracesEnl);
410+
polyLine.forEach((poly) => {
411+
L.polyline(poly, opts).addTo(window.plugin.playerTracker.getDrawnTracesByTeam(window.plugin.playerTracker.stored[playerName].team));
412+
});
415413
});
416-
});
417-
$.each(polyLineByAgeRes, function (i, polyLine) {
418-
if (polyLine.length === 0) return true;
419-
420-
var opts = {
421-
weight: 2 - 0.25 * i,
422-
color: window.PLAYER_TRACKER_LINE_COLOUR,
423-
interactive: false,
424-
opacity: 1 - 0.2 * i,
425-
dashArray: '5,8',
426-
};
414+
}
415+
};
427416

428-
$.each(polyLine, function (ind, poly) {
429-
L.polyline(poly, opts).addTo(window.plugin.playerTracker.drawnTracesRes);
430-
});
431-
});
417+
window.plugin.playerTracker.getDrawnTracesByTeam = function (team) {
418+
return team === 'RESISTANCE' ? window.plugin.playerTracker.drawnTracesRes : window.plugin.playerTracker.drawnTracesEnl;
432419
};
433420

434421
window.plugin.playerTracker.getPortalLink = function (data) {

test/utils.spec.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,38 @@ describe('IITC.utils.formatDistance', () => {
349349
});
350350
});
351351

352+
describe('IITC.utils.formatAgo', () => {
353+
const now = Date.now();
354+
355+
describe('Basic functionality', () => {
356+
it('should return "0s" when there is no time difference and seconds are enabled', () => {
357+
expect(IITC.utils.formatAgo(now, now, { showSeconds: true })).to.equal('0s');
358+
});
359+
360+
it('should return "0m" when time difference is negative and seconds are disabled', () => {
361+
const futureTime = now + 1000;
362+
expect(IITC.utils.formatAgo(futureTime, now)).to.equal('0m');
363+
});
364+
});
365+
366+
describe('Complex scenarios', () => {
367+
it('should not show seconds if seconds are disabled', () => {
368+
const time = now - 45 * 1000; // 45 seconds ago
369+
expect(IITC.utils.formatAgo(time, now)).to.equal('0m');
370+
});
371+
372+
it('should return only minutes if time difference is less than an hour', () => {
373+
const time = now - 5 * 60 * 1000; // 5 minutes ago
374+
expect(IITC.utils.formatAgo(time, now)).to.equal('5m');
375+
});
376+
377+
it('should handle all units enabled', () => {
378+
const time = now - (2 * 86400 + 5 * 3600 + 30 * 60 + 15) * 1000; // 2 days, 5 hours, 30 minutes, and 15 seconds ago
379+
expect(IITC.utils.formatAgo(time, now, { showSeconds: true })).to.equal('2d 5h 30m 15s');
380+
});
381+
});
382+
});
383+
352384
describe('IITC.utils.escapeJS', () => {
353385
it('should escape double quotes in the string', () => {
354386
const result = IITC.utils.escapeJS('Hello "World"');

0 commit comments

Comments
 (0)