Waze Editor Profile Enhancements

Pulls the correct forum post count - changed to red to signify the value as pulled from the forum by the script

当前为 2018-08-02 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Waze Editor Profile Enhancements
  3. // @namespace http://tampermonkey.net/
  4. // @version 2018.08.02.01
  5. // @description Pulls the correct forum post count - changed to red to signify the value as pulled from the forum by the script
  6. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAUCAYAAACXtf2DAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAADQklEQVRIS92VS0wTQRjHq+IrvmKiB8VEbxK9aIweNMZEEi/Gkxq6u4UCKjEEiQajMRobE7DdbrvS8DCF3Z3ty8oqiGAw4KMKxKpBxUcEORTF+EZFRQjPz5llbVqo0nj0n/wzO535ft/szDdbXbwyAUylrWgNxUk5NCsdpTm0h7LLGw446mZqUyYXAEwxmUwJWleVgXPPoTnZTrHoG2WRvutZsRc/DzJWuZ/h0A+c7Attk7MURZmmhcTWbjzBYJE2ptnlRO0nnRGhWZRV7KdYqQ8b/mgr+omTfsxwKIu10IliWGElZS7fkckK80gyhpU249XhwBjAGNaz0ihpIxcYFtkaxipso1hxS5bFuYCyoSQ8+UckIG5bURfj8MzX0GOizaULDZy0dZ/DtUXPCksZKzqO93ooJmAS06w4jAshW0OPibGULUvLlxNzCtHNPLGqAVcLPsjYgHisZ9Frk6LM0PA6XUaBuJiy+RaRM8gu9ifjrVL389+NRkibyVbPUxNkFytzqQJnEklAm+V1EwPI4bkgnXerbaxxYjKWccYDeAfUBZISVxPg2p/KFJRtZk5L62nOuxzX+/D4YOKq4BPwNz4E4VoQztY1gaP2FhTV3obSq00gNATVsZr7zwCf4eAhRZmtwolIFenNwtosp3M6qeUosBWpbTUO7OzuCftMTQBaQm/C/aevPwBXeR2a20KQWejt1NAThV9vUyrnGiTQdN4FB8svwoMI0G9X3n0Cz998Cvc73n+GiuZWQDfuDRh5t0/DRWt/sT+VgK897hhpf9sdBYzHra/egpF39abx8moNGa1UTu464antf/EuGh6KeB7v0Kevatvc9lLdSgzfpeEmyhQIJODXK9nr8H0vrw8O+W4/gL0OL16VG843PYoCN7Z3QnbJeVwpMhxz1YwY7a6fBhtiNNTfZbBJG402TxFe0fApfx3w1QFcetKoyXulJ0+41JdfUf8Nl+PQYbEKyuvvqCtneLRdC49f+GPXkuu8AEekSwTSg/sp+H8gFyejKYvYmFV0Dk56r5CxAYb3LNHCJhe5IAwnrqLMwk69RbxOYCk2KYVcSFLSRjNawbAoGd+Xyxge1FtQLvncaOH/lXS6Xw40MXnm6lDsAAAAAElFTkSuQmCC
  7. // @author JustinS83
  8. // @include https://www.waze.com/*user/editor*
  9. // @include https://beta.waze.com/*user/editor*
  10. // @grant GM_xmlhttpRequest
  11. // @require https://code.jquery.com/ui/1.12.1/jquery-ui.js
  12. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. var settings = {};
  19. var nawkts, rowwkts, ilwkts = [];
  20. var combinedNAWKT, combinedROWWKT, combinedILWKT= "";
  21. var naMA, rowMA, ilMA;
  22. const reducer = (accumulator, currentValue) => accumulator + currentValue;
  23.  
  24. function bootstrap(tries = 1) {
  25. if (W &&
  26. W.EditorProfile &&
  27. $) {
  28. init();
  29. } else if (tries < 1000) {
  30. setTimeout(function () {bootstrap(tries++);}, 200);
  31. }
  32. }
  33.  
  34. bootstrap();
  35.  
  36. async function init(){
  37. $('body').append('<span id="ruler" style="visibility:hidden; white-space:nowrap;"></span>');
  38. //injectCSS();
  39. loadSettings();
  40. String.prototype.visualLength = function(){ //measures the visual length of a string so we can better center the area labels on the areas
  41. var ruler = $("#ruler");
  42. ruler[0].innerHTML = this;
  43. return ruler[0].offsetWidth;
  44. }
  45. $.get('https://www.waze.com/forum/memberlist.php?username=' + W.EditorProfile.data.username, function(forumResult){
  46. var re = 0;
  47. var matches = forumResult.match(/<a.*?"Search user’s posts">(\d+)<\/a>/);
  48. if(matches && matches.length > 0)
  49. re = matches[1];
  50. var WazeVal = $('#header > div > div.user-info > div > div.user-highlights > div > div:nth-child(3) > div.user-stats-value')[0].innerHTML.trim();
  51. var userForumID = forumResult.match(/<a href="\.\/memberlist\.php\?mode=viewprofile&amp;u=(\d+)"/);
  52. if(userForumID != null){
  53. userForumID = userForumID[1];
  54. $('#header > div > div.user-info > div > div.user-highlights > div > div:nth-child(3) > div.highlight-title').css('position', 'relative');
  55.  
  56. if(WazeVal !== re.toString()){
  57. $('#header > div > div.user-info > div > div.user-highlights > div > div:nth-child(3) > div.user-stats-value')[0].innerHTML = re;
  58. $('#header > div > div.user-info > div > div.user-highlights > div > div:nth-child(3) > div.user-stats-value').css('color','red');
  59. $('#header > div > div.user-info > div > div.user-highlights > div > div:nth-child(3) > div.user-stats-value').prop('title', 'Waze reported value: ' + WazeVal);
  60. }
  61.  
  62. $('#header > div > div.user-info > div > div.user-highlights > div > div:nth-child(3)').wrap('<a href="https://www.waze.com/forum/search.php?author_id=' + userForumID + '&sr=posts" targ="_blank"></a>');
  63.  
  64. $('#header > div > div.user-info > div > div.user-highlights > a').prepend('<a href="https://www.waze.com/forum/memberlist.php?mode=viewprofile&u=' + userForumID +'" target="_blank" style="margin-right:5px;"><button class="message s-modern-button s-modern"><i class="fa fa-user"></i><span>Forum Profile</span></button></a>');
  65. }
  66. });
  67.  
  68. var count = 0;
  69. W.EditorProfile.data.editingActivity.forEach(function(x) { if(x !== 0) count++; });
  70. $('#editing-activity > div > h3').append(" (" + count + " of last 91 days)");
  71.  
  72. await getManagedAreas();
  73. BuildManagedAreasWKTInterface();
  74. /************** Add Average & Total to Editing Activity ***********/
  75. AddEditingActivityAvgandTot();
  76. /************** Add Editor Stats Section **************/
  77. AddEditorStatsSection();
  78. }
  79.  
  80. function AddLabelsToAreas(){
  81. $('svg.leaflet-zoom-animated g > text').remove();
  82. var svg = $('svg.leaflet-zoom-animated')[0];
  83. var pt = svg.createSVGPoint(), svgP;
  84.  
  85. let displayedAreas = $('svg.leaflet-zoom-animated g');
  86.  
  87. for(let i=0;i<displayedAreas.length;i++){
  88. let windowPosition = $(displayedAreas[i])[0].getBoundingClientRect();
  89. pt.x = (windowPosition.left + windowPosition.right) / 2;
  90. pt.y = (windowPosition.top + windowPosition.bottom) / 2;
  91. svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
  92.  
  93. if(svgP.x != 0 && svgP.y != 0){
  94. var newText = document.createElementNS("http://www.w3.org/2000/svg","text");
  95. newText.setAttributeNS(null,"x",svgP.x - (`Area ${i+1}`.visualLength() /2));
  96. newText.setAttributeNS(null,"y",svgP.y);
  97. newText.setAttributeNS(null, "fill", "red");
  98. newText.setAttributeNS(null,"font-size","12");
  99.  
  100. var textNode = document.createTextNode(`Area ${i+1}`);
  101. newText.appendChild(textNode);
  102. $(displayedAreas[i])[0].appendChild(newText);
  103. }
  104. }
  105. }
  106.  
  107. function BuildManagedAreasWKTInterface(){
  108. if(naMA.managedAreas.length > 0 || rowMA.managedAreas.length > 0 || ilMA.managedAreas.length > 0){
  109. $('#header > div > div.user-info > div > div.user-highlights > a').append('<a href="#" title="View editor\'s managed areas in WKT format"><button class="message s-modern-button s-modern" id="userMA"><i class="fa fa-map-o" aria-hidden="true"></i></button></a>');
  110.  
  111. /****** MO to update labels when panning/zooming the map ************/
  112. var observer = new MutationObserver(function(mutations) {
  113. mutations.forEach(function(mutation) {
  114. if ($(mutation.target).hasClass('leaflet-map-pane') && (mutation.attributeName === "class" || mutation.attributeName === "style")){
  115. if(mutation.attributeName === "class" && mutation.target.classList.length == 1) //zoom has ended, we can redraw our labels
  116. setTimeout(AddLabelsToAreas, 200);
  117. else if(mutation.attributeName === "style") //panning the map
  118. setTimeout(AddLabelsToAreas, 200);
  119. }
  120. });
  121. });
  122. observer.observe(document.getElementsByClassName('component-map-view')[0], { childList: true, subtree: true, attributes:true });
  123.  
  124. AddLabelsToAreas();
  125.  
  126. $('#userMA').click(function(){
  127. if($('#wpeWKT').css('visibility') === 'visible')
  128. $('#wpeWKT').css({'visibility': 'hidden'});
  129. else
  130. $('#wpeWKT').css({'visibility': 'visible'});
  131. });
  132.  
  133. var result = buildWKTArray(naMA);
  134. nawkts = result.wktArr;
  135. combinedNAWKT = result.combinedWKT;
  136.  
  137. result = buildWKTArray(rowMA);
  138. rowwkts = result.wktArr;
  139. combinedROWWKT = result.combinedWKT;
  140.  
  141. result = buildWKTArray(ilMA);
  142. ilwkts = result.wktArr;
  143. combinedILWKT = result.combinedWKT;
  144.  
  145. var $section = $("<div>", {style:"padding:8px 16px"});
  146. $section.html([
  147. '<div id="wpeWKT" style="padding:8px 16px; position:fixed; border-radius:10px; box-shadow:5px 5px 10px 4px Silver; top:25%; left:40%; background-color:white; visibility:hidden;">', //Main div
  148. '<div style="float:right; cursor:pointer;" id="wpeClose"><i class="fa fa-window-close" aria-hidden="true"></i></div>',
  149. '<ul class="nav nav-tabs">',
  150. `${naMA.managedAreas.length > 0 ? '<li class="active"><a data-toggle="pill" href="#naAreas">NA</a></li>' : ''}`,
  151. `${rowMA.managedAreas.length > 0 ? '<li><a data-toggle="pill" href="#rowAreas">ROW</a></li>' : ''}`,
  152. `${ilMA.managedAreas.length > 0 ? '<li><a data-toggle="pill" href="#ilAreas">IL</a></li>' : ''}`,
  153. '</ul>',
  154. '<div class="tab-content">',
  155. '<div id="naAreas" class="tab-pane fade in active">',
  156. '<div id="wpenaAreas" style="float:left; max-height:350px; overflow:auto;"><h3 style="float:left; left:50%;">Editor Areas</h3><br>' + buildAreaList(nawkts,"na") + '</div>',
  157. '<div id="wpenaPolygons" style="float:left; padding-left:15px;"><h3 style="position:relative; float:left; left:40%;">Area WKT</h3><br><textarea rows="7" cols="55" id="wpenaAreaWKT" style="height:auto;"></textarea></div>',
  158. '</div>',//naAreas
  159. '<div id="rowAreas" class="tab-pane fade">',
  160. '<div id="wperowAreas" style="float:left; max-height:350px; overflow:auto;"><h3 style="float:left; left:50%;">Editor Areas</h3><br>' + buildAreaList(rowwkts, "row") + '</div>',
  161. '<div id="wperowPolygons" style="float:left; padding-left:15px;"><h3 style="position:relative; float:left; left:40%;">Area WKT</h3><br><textarea rows="7" cols="55" id="wperowAreaWKT" style="height:auto;"></textarea></div>',
  162. '</div>',//rowAreas
  163. '<div id="ilAreas" class="tab-pane fade">',
  164. '<div id="wpeilAreas" style="float:left; max-height:350px; overflow:auto;"><h3 style="float:left; left:50%;">Editor Areas</h3><br>' + buildAreaList(ilwkts, "il") + '</div>',
  165. '<div id="wpeilPolygons" style="float:left; padding-left:15px;"><h3 style="position:relative; float:left; left:40%;">Area WKT</h3><br><textarea rows="7" cols="55" id="wpeilAreaWKT" style="height:auto;"></textarea></div>',
  166. '</div>',//ilAreas
  167. '<div id="wpeFooter" style="clear:both; margin-top:10px;">View the areas by entering the WKT at <a href="http://map.wazedev.com" target="_blank">http://map.wazedev.com</a></div>',
  168. '</div>', //tab-content
  169. '</div>' //end main div
  170. ].join(' '));
  171.  
  172. $('body').append($section.html());
  173.  
  174. $('[id^="wpenaAreaButton"]').click(function(){
  175. let index = parseInt($(this)[0].id.replace("wpenaAreaButton", ""));
  176. $('#wpenaAreaWKT').text(nawkts[index]);
  177. $('#wpenaPolygons > h3').text(`Area ${index+1} WKT`);
  178. });
  179.  
  180. $('[id^="wperowAreaButton"]').click(function(){
  181. let index = parseInt($(this)[0].id.replace("wperowAreaButton", ""));
  182. $('#wperowAreaWKT').text(rowwkts[index]);
  183. $('#wperowPolygons > h3').text(`Area ${index+1} WKT`);
  184. });
  185.  
  186. $('[id^="wpeilAreaButton"]').click(function(){
  187. let index = parseInt($(this)[0].id.replace("wpeilAreaButton", ""));
  188. $('#wpeilAreaWKT').text(ilwkts[index]);
  189. $('#wpeilPolygons > h3').text(`Area ${index+1} WKT`);
  190. });
  191.  
  192. $('#wpenaCombinedAreaButton').click(function(){
  193. $('#wpenaAreaWKT').text(combinedNAWKT);
  194. $('#wpenaPolygons > h3').text(`Combined Area WKT`);
  195. });
  196.  
  197. $('#wperowCombinedAreaButton').click(function(){
  198. $('#wperowAreaWKT').text(combinedROWWKT);
  199. $('#wperowPolygons > h3').text(`Combined Area WKT`);
  200. });
  201.  
  202. $('#wpeilCombinedAreaButton').click(function(){
  203. $('#wpeilAreaWKT').text(combinedILWKT);
  204. $('#wpeilPolygons > h3').text(`Combined Area WKT`);
  205. });
  206.  
  207. $('#wpeClose').click(function(){
  208. if($('#wpeWKT').css('visibility') === 'visible')
  209. $('#wpeWKT').css({'visibility': 'hidden'});
  210. else
  211. $('#wpeWKT').css({'visibility': 'visible'});
  212. });
  213. }
  214. }
  215.  
  216. function AddEditorStatsSection(){
  217. let edits = W.EditorProfile.data.edits
  218. let editActivity = [].concat(W.EditorProfile.data.editingActivity);
  219. let rank = W.EditorProfile.data.rank+1;
  220. let count = 0;
  221. editActivity.forEach(function(x) {if(x !== 0) count++; });
  222. let editAverageDailyActive = Math.round(editActivity.reduce(reducer)/count);
  223. let editAverageDaily = Math.round(editActivity.reduce(reducer)/91);
  224.  
  225. var $editorProgress = $("<div>");
  226. $editorProgress.html([
  227. `<div id="collapsible" style="display:${settings.EditingStatsExpanded ? "block" : "none"};">`,
  228. '<div style="display:inline-block;"><div><h4>Average Edits per Day</h4></div><div>' + editAverageDaily + '</div></div>',
  229. '<div style="display:inline-block; margin-left:25px;"><div><h4>Average Edits per Day (active days only)</h4></div><div>' + editAverageDailyActive + '</div></div>',
  230. '<div class="editor-progress-list" style="display:flex; flex-flow:row wrap; justify-content:space-around;">',
  231. buildProgressItemsHTML(),
  232. '</div>'
  233. ].join(' '));
  234.  
  235. $('#editing-activity').append('<div id="editor-progress"><h3 id="collapseHeader" style="cursor:pointer;">Editing Stats</h3></div>');
  236. $('#editor-progress').append($editorProgress.html()+'</div>');
  237.  
  238. $('#collapseHeader').click(function(){
  239. $('#collapsible').toggle();
  240. settings.EditingStatsExpanded = ($('#collapsible').css("display") === "block");
  241. saveSettings();
  242. });
  243. }
  244.  
  245. function buildProgressItemsHTML(){
  246. var itemsArr = [];
  247. var $items = $("<div>");
  248. let editActivity = W.EditorProfile.data.editingActivity;
  249.  
  250. //loop over the 13 tracked weeks on the profile
  251. for(let i=0; i<13; i++){
  252. let header = "";
  253. let weekEditCount = 0;
  254. //let weekEditPct = 0;
  255. if(i==0){
  256. header = "Past 7 days";
  257. weekEditCount = editActivity.slice(-7).reduce(reducer);
  258. }
  259. else{
  260. header = `Past ${i*7+1} - ${(i+1)*7} days`;
  261. weekEditCount = editActivity.slice(-((i+1)*7),-i*7).reduce(reducer);
  262. }
  263. let weekDailyAvg = Math.round(weekEditCount/7*100)/100;
  264. itemsArr.push('<div style="margin-right:20px;">');
  265. itemsArr.push(`<h4>${header}</h4>`);
  266. itemsArr.push('<div class="editor-progress-item">');
  267. itemsArr.push(`<div class="editor-progress__name">Week\'s Edits</div><div class="editor-progress__count">${weekEditCount}</div>`); //, ${weekEditPct}%</div>`);
  268. itemsArr.push(`<div class="editor-progress__name">Average Edits/Day</div><div class="editor-progress__count">${weekDailyAvg}</div>`);
  269. itemsArr.push('</div></div>');
  270. }
  271. $items.html(itemsArr.join(' '));
  272. return $items.html();
  273. }
  274.  
  275. function AddEditingActivityAvgandTot(){
  276. $('.legend').append('<div class="day-initial">Avg</div> <div class="day-initial">Tot</div>');
  277. $('.editing-activity').css({"width":"1010px"}); //With adding the Avg and Tot rows we have to widen the div a little so it doesn't wrap one of the columns
  278.  
  279. let currWeekday = new Date().getDay();
  280. if(currWeekday === 0)
  281. currWeekday = 7;
  282. let localEditActivity = [].concat(W.EditorProfile.data.editingActivity);
  283. let weekEditsArr = localEditActivity.splice(-currWeekday);
  284. let weekEditsCount = weekEditsArr.reduce(reducer);
  285. var iteratorStart = 13;
  286. if(currWeekday === 7)
  287. iteratorStart = 12;
  288. $(`.weeks div:nth-child(${iteratorStart+1}) .week`).append(`<div class="day" style="font-size:10px; height:10px; text-align:center; margin-top:-5px;" title="Average edits per day for this week">${Math.round(weekEditsCount/currWeekday * 100) / 100}</div><div style="font-size:10px; height:10px; text-align:center;" title="Total edits for this week">${weekEditsCount}</div>`);
  289. for(let i=iteratorStart; i>0; i--){
  290. weekEditsArr = localEditActivity.splice(-7);
  291. weekEditsCount = weekEditsArr.splice(-7).reduce(reducer);
  292. let avg = Math.round(weekEditsCount/7 * 100) / 100;
  293. $(`.weeks div:nth-child(${i}) .week`).append(`<div class="day" style="font-size:10px; height:10px; text-align:center; margin-top:-5px;" title="Average edits per day for this week">${avg}</div><div style="font-size:10px; height:10px; text-align:center;" title="Total edits for this week">${weekEditsCount}</div>`);
  294. }
  295. }
  296.  
  297. function buildAreaList(wkts, server){
  298. let html = "";
  299. for(let i=0; i<wkts.length; i++){
  300. html +=`<button id="wpe${server}AreaButton${i}" class="s-button s-button--mercury " style="margin-bottom:5px;">Area ${i+1}</button><br>`;
  301. }
  302. if(wkts.length > 1)
  303. html +=`<button id="wpe${server}CombinedAreaButton" class="s-button s-button--mercury " style="margin-bottom:5px;">Combined</button><br>`;
  304. return html;
  305. }
  306.  
  307. function buildWKTArray(wktObj){
  308. let wkt = "";
  309. let combined = "";
  310. let wktArr = [];
  311. for(let i=0; i<wktObj.managedAreas.length; i++){
  312. if(i>0)
  313. combined += ",";
  314. wkt = "";
  315. combined += "(";
  316. for(let j=0; j<wktObj.managedAreas[i].coordinates.length; j++){
  317. if(j>0){
  318. wkt += ",";
  319. combined += ",";
  320. }
  321. combined += "(";
  322. wkt +="(";
  323. for(let k=0; k<wktObj.managedAreas[i].coordinates[j].length; k++){
  324. if(k > 0){
  325. wkt+=", ";
  326. combined += ",";
  327. }
  328. wkt += round(parseFloat(wktObj.managedAreas[i].coordinates[j][k][0])).toString() + " " + round(parseFloat(wktObj.managedAreas[i].coordinates[j][k][1])).toString();
  329. combined += round(parseFloat(wktObj.managedAreas[i].coordinates[j][k][0])).toString() + " " + round(parseFloat(wktObj.managedAreas[i].coordinates[j][k][1])).toString();
  330. }
  331. combined += ")";
  332. wkt += ")";
  333. }
  334. combined += ")";
  335. wkt = `POLYGON${wkt}`;
  336. wktArr.push(wkt);
  337. }
  338. if(wktObj.managedAreas.length > 1)
  339. combined = `MULTIPOLYGON(${combined})` ;
  340. else
  341. combined = `POLYGON${combined}`;
  342.  
  343. return {wktArr: wktArr, combinedWKT: combined};
  344. }
  345.  
  346. function round(val){
  347. return Math.round(val*1000000)/1000000;
  348. }
  349.  
  350. async function getManagedAreas(){
  351. naMA = await $.get(`https://www.waze.com/Descartes/app/UserProfile/Areas?userID=${W.EditorProfile.data.userID}`);
  352. rowMA = await $.get(`https://www.waze.com/row-Descartes/app/UserProfile/Areas?userID=${W.EditorProfile.data.userID}`);
  353. ilMA = await $.get(`https://www.waze.com/il-Descartes/app/UserProfile/Areas?userID=${W.EditorProfile.data.userID}`);
  354.  
  355. /*return await new W.EditorProfile.Models.ManagedAreas([],{
  356. lastEditEnv: 'na',
  357. userId: W.EditorProfile.data.userID
  358. }).fetch();*/
  359. }
  360.  
  361. function injectCSS() {
  362. /*var css = [
  363. ].join(' ');
  364. $('<style type="text/css">' + css + '</style>').appendTo('head');*/
  365. }
  366.  
  367. function loadSettings() {
  368. var loadedSettings = $.parseJSON(localStorage.getItem("WEPE_Settings"));
  369. var defaultSettings = {
  370. EditingStatsExpanded: true
  371. };
  372. settings = loadedSettings ? loadedSettings : defaultSettings;
  373. for (var prop in defaultSettings) {
  374. if (!settings.hasOwnProperty(prop))
  375. settings[prop] = defaultSettings[prop];
  376. }
  377. }
  378.  
  379. function saveSettings() {
  380. if (localStorage) {
  381. var localsettings = {
  382. EditingStatsExpanded: settings.EditingStatsExpanded,
  383. };
  384.  
  385. localStorage.setItem("WEPE_Settings", JSON.stringify(localsettings));
  386. }
  387. }
  388. })();