WME HN NavPoints

Shows navigation points of all house numbers in WME

当前为 2021-07-28 提交的版本,查看 最新版本

  1. /* eslint-disable no-template-curly-in-string */
  2. // ==UserScript==
  3. // @name WME HN NavPoints
  4. // @namespace https://greasyfork.org/users/166843
  5. // @description Shows navigation points of all house numbers in WME
  6. // @version 2021.07.28.01
  7. // @author dBsooner
  8. // @grant none
  9. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  10. // @license GPLv3
  11. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
  12. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  13. // ==/UserScript==
  14.  
  15. /* global _, $, document, GM_info, localStorage, MutationObserver, OpenLayers, performance, W, WazeWrap, window */
  16.  
  17. /*
  18. * Original concept and code for WME HN NavPoints was written by MajkiiTelini. After version 0.6.6, this
  19. * script is maintained by the WazeDev team. Special thanks is definitely given to MajkiiTelini for his
  20. * hard work and dedication to the original script.
  21. *
  22. */
  23.  
  24. const ALERT_UPDATE = true,
  25. DEBUG = false,
  26. LOAD_BEGIN_TIME = performance.now(),
  27. SCRIPT_FORUM_URL = 'https://www.waze.com/forum/viewtopic.php?f=819&t=289116',
  28. SCRIPT_GF_URL = 'https://greasyfork.org/en/scripts/390565-wme-hn-navpoints',
  29. SCRIPT_NAME = GM_info.script.name.replace('(beta)', 'β'),
  30. SCRIPT_VERSION = GM_info.script.version,
  31. SCRIPT_VERSION_CHANGES = [
  32. '<b>NEW:</b> Fix WME bug by forcing WME to clean its HN object array when exiting house numbers mode, thus preventing WME from growing the array astonishingly large, wasting resources.',
  33. '<b>NEW:</b> Multiple new functions to aide in the bugfixes named below.',
  34. '<b>CHANGE:</b> Allow HNs to be drawn concurrently with other map features. (MUCH faster)',
  35. '<b>CHANGE:</b> WazeWrap.Requires.Icon class used now instead of injecting my own OpenLayers.Icon class.',
  36. '<b>CHANGE:</b> Less data stored within HNs and Lines objects.',
  37. '<b>CHANGE:</b> Now using the new WME font for HNs.',
  38. '<b>BUGFIX:</b> Clicking the reload / refresh icon in WME would not clear HNs or Lines in some instances.',
  39. '<b>BUGFIX:</b> HNs and Lines were either removed, not removed or were multiplied (multiple numbers and lines for same HN) in certain situations.',
  40. '<b>BUGFIX:</b> Selecting a house number input field would not remove the HN or Line in certain situations.'
  41. ],
  42. SETTINGS_STORE_NAME = 'WMEHNNavPoints',
  43. _spinners = {
  44. destroyAllHNs: false,
  45. drawHNs: false,
  46. processSegs: false
  47. },
  48. _timeouts = {
  49. bootstrap: undefined,
  50. hideTooltip: undefined,
  51. saveSettingsToStorage: undefined,
  52. setMarkersEvents: undefined
  53. };
  54.  
  55. let _settings = {},
  56. _scriptActive = false,
  57. _HNLayerObserver,
  58. _saveButtonObserver,
  59. _HNNavPointsLayer,
  60. _HNNavPointsNumbersLayer,
  61. _wmeHnLayer,
  62. _processedSegments = [],
  63. _segmentsToProcess = [],
  64. _segmentsToRemove = [],
  65. _$hnNavPointsTooltipDiv,
  66. /* 2020.07.16.01 - Removed in favor of dual layer types: one for Vector (no tooltip popup) and one for marker (tooltip popup).
  67. Prior to this it was an attempt to work with several OL SelectFeatures controllers. However, it doesn't seem possible with OL 2.
  68. _hnMouseoverCtrl,
  69. */
  70. _popup = {
  71. inUse: false,
  72. hnNumber: -1,
  73. segmentId: -1
  74. };
  75.  
  76. function log(message) { console.log('WME-HN-NavPoints:', message); }
  77. function logError(message) { console.error('WME-HN-NavPoints:', message); }
  78. // function logWarning(message) { console.warn('WME-HN-NavPoints:', message); }
  79. function logDebug(message) {
  80. if (DEBUG)
  81. console.log('WME-HN-NavPoints:', message);
  82. }
  83.  
  84. async function loadSettingsFromStorage() {
  85. const defaultSettings = {
  86. disableBelowZoom: 5,
  87. enableTooltip: true,
  88. hnLines: true,
  89. hnNumbers: true,
  90. keepHNLayerOnTop: true,
  91. toggleHNNavPointsShortcut: '',
  92. toggleHNNavPointsNumbersShortcut: '',
  93. lastSaved: 0,
  94. lastVersion: undefined
  95. },
  96. loadedSettings = $.parseJSON(localStorage.getItem(SETTINGS_STORE_NAME));
  97. _settings = $.extend({}, defaultSettings, loadedSettings);
  98. const serverSettings = await WazeWrap.Remote.RetrieveSettings(SETTINGS_STORE_NAME);
  99. if (serverSettings && (serverSettings.lastSaved > _settings.lastSaved))
  100. $.extend(_settings, serverSettings);
  101. _timeouts.saveSettingsToStorage = window.setTimeout(saveSettingsToStorage, 5000);
  102.  
  103. return Promise.resolve();
  104. }
  105.  
  106. function saveSettingsToStorage() {
  107. checkTimeout({ timeout: 'saveSettingsToStorage' });
  108. if (localStorage) {
  109. _settings.lastVersion = SCRIPT_VERSION;
  110. _settings.lastSaved = Date.now();
  111. localStorage.setItem(SETTINGS_STORE_NAME, JSON.stringify(_settings));
  112. WazeWrap.Remote.SaveSettings(SETTINGS_STORE_NAME, _settings);
  113. logDebug('Settings saved.');
  114. }
  115. }
  116.  
  117. function showScriptInfoAlert() {
  118. if (ALERT_UPDATE && SCRIPT_VERSION !== _settings.lastVersion) {
  119. let releaseNotes = '';
  120. releaseNotes += '<p>What\'s New:</p>';
  121. if (SCRIPT_VERSION_CHANGES.length > 0) {
  122. releaseNotes += '<ul>';
  123. for (let idx = 0; idx < SCRIPT_VERSION_CHANGES.length; idx++)
  124. releaseNotes += `<li>${SCRIPT_VERSION_CHANGES[idx]}`;
  125. releaseNotes += '</ul>';
  126. }
  127. else {
  128. releaseNotes += '<ul><li>Nothing major.</ul>';
  129. }
  130. WazeWrap.Interface.ShowScriptUpdate(SCRIPT_NAME, SCRIPT_VERSION, releaseNotes, SCRIPT_GF_URL, SCRIPT_FORUM_URL);
  131. }
  132. }
  133.  
  134. function checkShortcutsChanged() {
  135. let triggerSave = false;
  136. ['toggleHNNavPointsShortcut', 'toggleHNNavPointsNumbersShortcut'].forEach(k => {
  137. let keys = '';
  138. const { shortcut } = W.accelerators.Actions[k];
  139. if (shortcut) {
  140. if (shortcut.altKey)
  141. keys += 'A';
  142. if (shortcut.shiftKey)
  143. keys += 'S';
  144. if (shortcut.ctrlKey)
  145. keys += 'C';
  146. if (keys !== '')
  147. keys += '+';
  148. if (shortcut.keyCode)
  149. keys += shortcut.keyCode;
  150. }
  151. else {
  152. keys = '';
  153. }
  154. if (_settings[k] !== keys) {
  155. _settings[k] = keys;
  156. triggerSave = true;
  157. }
  158. });
  159. if (triggerSave)
  160. saveSettingsToStorage();
  161. }
  162.  
  163. function checkTimeout(obj) {
  164. if (obj.toIndex) {
  165. if (_timeouts[obj.timeout] && (_timeouts[obj.timeout][obj.toIndex] !== undefined)) {
  166. window.clearTimeout(_timeouts[obj.timeout][obj.toIndex]);
  167. _timeouts[obj.timeout][obj.toIndex] = undefined;
  168. }
  169. }
  170. else {
  171. if (_timeouts[obj.timeout] !== undefined)
  172. window.clearTimeout(_timeouts[obj.timeout]);
  173. _timeouts[obj.timeout] = undefined;
  174. }
  175. }
  176.  
  177. function doSpinner(spinnerName = '', spin = true) {
  178. const $btn = $('#hnNPSpinner');
  179. if (!spin) {
  180. _spinners[spinnerName] = false;
  181. if (!Object.values(_spinners).some(a => a === true)) {
  182. if ($btn.length > 0) {
  183. $btn.removeClass('fa-spin');
  184. $('#divHnNPSpinner').hide();
  185. }
  186. else {
  187. $('#topbar-container .topbar').prepend(
  188. '<div id="divHnNPSpinner" title="WME HN NavPoints is currently processing house numbers." style="font-size:20px;background:white;float:left;margin-left:-20px;display:none;">'
  189. + '<i id="hnNPSpinner" class="fa fa-spinner"></i></div>'
  190. );
  191. }
  192. }
  193. }
  194. else {
  195. _spinners[spinnerName] = true;
  196. if ($btn.length === 0) {
  197. _spinners[spinnerName] = true;
  198. $('#topbar-container .topbar').prepend(
  199. '<div id="divHnNPSpinner" title="WME HN NavPoints is currently processing house numbers." style="font-size:20px;background:white;float:left;margin-left:-20px;">'
  200. + '<i id="hnNPSpinner" class="fa fa-spinner fa-spin"></i></div>'
  201. );
  202. }
  203. else if (!$btn.hasClass('fa-spin')) {
  204. $btn.addClass('fa-spin');
  205. $('#divHnNPSpinner').show();
  206. }
  207. }
  208. }
  209.  
  210. function processSegmentsToRemove() {
  211. if (_segmentsToRemove.length > 0) {
  212. const removeMarker = marker => { _HNNavPointsNumbersLayer.removeMarker(marker); };
  213. let linesToRemove = [],
  214. hnsToRemove = [];
  215. for (let i = _segmentsToRemove.length - 1; i > -1; i--) {
  216. const segId = _segmentsToRemove[i];
  217. if (!W.model.segments.objects[segId]) {
  218. _segmentsToRemove.splice(i, 1);
  219. linesToRemove = linesToRemove.concat(_HNNavPointsLayer.getFeaturesByAttribute('segmentId', segId));
  220. if (!_settings.enableTooltip)
  221. hnsToRemove = hnsToRemove.concat(_HNNavPointsNumbersLayer.getFeaturesByAttribute('segmentId', segId));
  222. else
  223. _HNNavPointsNumbersLayer.markers.filter(marker => marker.segmentId === segId).forEach(marker => removeMarker(marker));
  224. }
  225. }
  226. if (linesToRemove.length > 0)
  227. _HNNavPointsLayer.removeFeatures(linesToRemove);
  228. if (hnsToRemove.length > 0)
  229. _HNNavPointsNumbersLayer.removeFeatures(hnsToRemove);
  230. }
  231. }
  232.  
  233. async function hnLayerToggled(checked) {
  234. _HNNavPointsLayer.setVisibility(checked);
  235. _settings.hnLines = checked;
  236. saveSettingsToStorage();
  237. if (checked) {
  238. if (!_scriptActive)
  239. await initBackgroundTasks('enable');
  240. processSegs('hnLayerToggled', W.model.segments.getByAttributes({ hasHNs: true }));
  241. }
  242. else if (!_settings.hnNumbers && _scriptActive) {
  243. initBackgroundTasks('disable');
  244. }
  245. }
  246.  
  247. async function hnNumbersLayerToggled(checked) {
  248. _HNNavPointsNumbersLayer.setVisibility(checked);
  249. _settings.hnNumbers = checked;
  250. saveSettingsToStorage();
  251. if (checked) {
  252. if (!_scriptActive)
  253. await initBackgroundTasks('enable');
  254. processSegs('hnNumbersLayerToggled', W.model.segments.getByAttributes({ hasHNs: true }));
  255. }
  256. else if (!_settings.hnLines && _scriptActive) {
  257. initBackgroundTasks('disable');
  258. }
  259. }
  260.  
  261. function observeHNLayer() {
  262. if (W.editingMediator.attributes.editingHouseNumbers && !_HNLayerObserver.observing) {
  263. [_wmeHnLayer] = W.map.getLayersByName('houseNumberMarkers');
  264. _HNLayerObserver.observe(_wmeHnLayer.div, { childList: false, subtree: true, attributes: true });
  265. _HNLayerObserver.observing = true;
  266. }
  267. else if (_HNLayerObserver.observing) {
  268. _HNLayerObserver.disconnect();
  269. _HNLayerObserver.observing = false;
  270. }
  271. if (!_HNLayerObserver.observing) {
  272. W.model.segmentHouseNumbers.clear();
  273. processSegs('exithousenumbers', W.model.segments.getByIds(_segmentsToProcess), true);
  274. processSegmentsToRemove();
  275. _wmeHnLayer = undefined;
  276. }
  277. else {
  278. _segmentsToProcess = W.selectionManager.getSegmentSelection().segments.map(segment => segment.attributes.id);
  279. _segmentsToRemove = [];
  280. }
  281. _saveButtonObserver.disconnect();
  282. _saveButtonObserver.observe($('#edit-buttons .waze-icon-save')[0], {
  283. childList: false, attributes: true, attributeOldValue: true, characterData: false, characterDataOldValue: false, subtree: false
  284. });
  285. }
  286.  
  287. function removeHNs(objArr) {
  288. let linesToRemove = [],
  289. hnsToRemove = [];
  290. objArr.forEach(hnObj => {
  291. linesToRemove = linesToRemove.concat(_HNNavPointsLayer.getFeaturesByAttribute('featureId', hnObj.attributes.id));
  292. if (!_settings.enableTooltip)
  293. hnsToRemove = hnsToRemove.concat(_HNNavPointsNumbersLayer.getFeaturesByAttribute('featureId', hnObj.attributes.id));
  294. else
  295. _HNNavPointsNumbersLayer.markers.filter(a => a.featureId === hnObj.attributes.id).forEach(marker => { _HNNavPointsNumbersLayer.removeMarker(marker); });
  296. });
  297. if (linesToRemove.length > 0)
  298. _HNNavPointsLayer.removeFeatures(linesToRemove);
  299. if (hnsToRemove.length > 0)
  300. _HNNavPointsNumbersLayer.removeFeatures(hnsToRemove);
  301. }
  302.  
  303. function drawHNs(houseNumberArr) {
  304. if (houseNumberArr.length === 0)
  305. return;
  306. doSpinner('drawHNs', true);
  307. const lineFeatures = [],
  308. numberFeatures = !_settings.enableTooltip ? [] : undefined,
  309. svg = _settings.enableTooltip ? document.createElementNS('http://www.w3.org/2000/svg', 'svg') : undefined,
  310. svgText = _settings.enableTooltip ? document.createElementNS('http://www.w3.org/2000/svg', 'text') : undefined,
  311. invokeTooltip = _settings.enableTooltip ? evt => { showTooltip(evt); } : undefined;
  312. if (_settings.enableTooltip) {
  313. svg.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
  314. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  315. svg.setAttribute('viewBox', '0 0 40 14');
  316. svgText.setAttribute('text-anchor', 'middle');
  317. svgText.setAttribute('x', '20');
  318. svgText.setAttribute('y', '10');
  319. }
  320. for (let i = 0; i < houseNumberArr.length; i++) {
  321. const hnObj = houseNumberArr[i],
  322. segmentId = hnObj.getSegmentId(),
  323. seg = W.model.segments.objects[segmentId];
  324. if (seg) {
  325. const featureId = hnObj.getID(),
  326. markerIdx = _settings.enableTooltip ? _HNNavPointsNumbersLayer.markers.map(marker => marker.featureId).indexOf(featureId) : undefined,
  327. // eslint-disable-next-line no-nested-ternary
  328. hnToRemove = _settings.enableTooltip ? (markerIdx > -1) ? _HNNavPointsNumbersLayer.markers[markerIdx] : [] : _HNNavPointsNumbersLayer.getFeaturesByAttribute('featureId', featureId),
  329. rtlChar = /[\u0590-\u083F]|[\u08A0-\u08FF]|[\uFB1D-\uFDFF]|[\uFE70-\uFEFF]/mg,
  330. textDir = (hnObj.getNumber().match(rtlChar) !== null) ? 'rtl' : 'ltr';
  331. _HNNavPointsLayer.removeFeatures(_HNNavPointsLayer.getFeaturesByAttribute('featureId', featureId));
  332. if (hnToRemove.length > 0) {
  333. if (_settings.enableTooltip)
  334. _HNNavPointsNumbersLayer.removeMarker(hnToRemove);
  335. else
  336. _HNNavPointsNumbersLayer.removeFeatures(_HNNavPointsNumbersLayer.getFeaturesByAttribute('featureId', featureId));
  337. }
  338. const p1 = new OpenLayers.Geometry.Point(hnObj.getFractionPoint().x, hnObj.getFractionPoint().y),
  339. p2 = new OpenLayers.Geometry.Point(hnObj.getGeometry().x, hnObj.getGeometry().y),
  340. // eslint-disable-next-line no-nested-ternary
  341. strokeColor = (hnObj.isForced()
  342. ? (!hnObj.getUpdatedBy()) ? 'red' : 'orange'
  343. : (!hnObj.getUpdatedBy()) ? 'yellow' : 'white'
  344. );
  345. let lineString = new OpenLayers.Geometry.LineString([p1, p2]),
  346. lineFeature = new OpenLayers.Feature.Vector(
  347. lineString,
  348. { segmentId, featureId },
  349. {
  350. strokeWidth: 4, strokeColor: 'black', strokeOpacity: 0.5, strokeDashstyle: 'dash', strokeDashArray: '8, 8'
  351. }
  352. );
  353. lineFeatures.push(lineFeature);
  354. lineString = new OpenLayers.Geometry.LineString([p1, p2]);
  355. lineFeature = new OpenLayers.Feature.Vector(
  356. lineString,
  357. { segmentId, featureId },
  358. {
  359. strokeWidth: 2, strokeColor, strokeOpacity: 1, strokeDashstyle: 'dash', strokeDashArray: '8, 8'
  360. }
  361. );
  362. lineFeatures.push(lineFeature);
  363. if (_settings.enableTooltip) {
  364. svg.setAttribute('style', `text-shadow:0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor};font-size:14px;font-weight:bold;font-family:"Open Sans", "Arial Unicode MS", "sans-serif";direction:${textDir}`);
  365. svgText.textContent = hnObj.getNumber();
  366. svg.innerHTML = svgText.outerHTML;
  367. const svgIcon = new WazeWrap.Require.Icon(`data:image/svg+xml,${svg.outerHTML}`, { w: 40, h: 18 }),
  368. markerFeature = new OpenLayers.Marker(new OpenLayers.LonLat(p2.x, p2.y), svgIcon);
  369. markerFeature.events.register('mouseover', null, invokeTooltip);
  370. markerFeature.events.register('mouseout', null, hideTooltipDelay);
  371. markerFeature.featureId = featureId;
  372. markerFeature.segmentId = segmentId;
  373. markerFeature.hnNumber = hnObj.getNumber() || '';
  374. _HNNavPointsNumbersLayer.addMarker(markerFeature);
  375. }
  376. else {
  377. // eslint-disable-next-line new-cap
  378. numberFeatures.push(new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon.createRegularPolygon(p2, 1, 20), {
  379. segmentId, featureId, hn_number: hnObj.getNumber(), strokeWidth: 3, Color: strokeColor, textDir
  380. }));
  381. }
  382. }
  383. }
  384. if (lineFeatures.length > 0)
  385. _HNNavPointsLayer.addFeatures(lineFeatures);
  386. if (!_settings.enableTooltip && (numberFeatures.length > 0))
  387. _HNNavPointsNumbersLayer.addFeatures(numberFeatures);
  388. doSpinner('drawHNs', false);
  389. }
  390.  
  391. function destroyAllHNs() {
  392. doSpinner('destroyAllHNs', true);
  393. _HNNavPointsLayer.destroyFeatures();
  394. if (_settings.enableTooltip)
  395. _HNNavPointsNumbersLayer.clearMarkers();
  396. else
  397. _HNNavPointsNumbersLayer.destroyFeatures();
  398. _processedSegments = [];
  399. doSpinner('destroyAllHNs', false);
  400. Promise.resolve();
  401. }
  402.  
  403. function processSegs(action, arrSegObjs, processAll = false, retry = 0) {
  404. /* As of 2020.06.08 (sometime before this date) updatedOn does not get updated when updating house numbers. Looking for a new
  405. * way to track which segments have been updated most recently to prevent a total refresh of HNs after an event.
  406. * Changed to using a global to keep track of segmentIds touched during HN edit mode.
  407. */
  408. if ((action === 'settingChanged') && (W.map.getZoom() < _settings.disableBelowZoom)) {
  409. destroyAllHNs();
  410. return;
  411. }
  412. if (!arrSegObjs || (arrSegObjs.length === 0) || (W.map.getZoom() < _settings.disableBelowZoom) || preventProcess())
  413. return;
  414. doSpinner('processSegs', true);
  415. const eg = W.map.getExtent().toGeometry(),
  416. findObjIndex = (array, fldName, value) => array.map(a => a[fldName]).indexOf(value),
  417. processError = (err, chunk) => {
  418. logDebug(`Retry: ${retry}`);
  419. if (retry < 5)
  420. processSegs(action, chunk, true, ++retry);
  421. else
  422. logError(`Get HNs for ${chunk.length} segments failed. Code: ${err.status} - Text: ${err.responseText}`);
  423. },
  424. processJSON = jsonData => {
  425. if (jsonData && (jsonData.error === undefined) && (typeof jsonData.segmentHouseNumbers.objects !== 'undefined'))
  426. drawHNs(jsonData.segmentHouseNumbers.objects);
  427. };
  428. if ((action === 'objectsremoved')) {
  429. if (arrSegObjs && (arrSegObjs.length > 0)) {
  430. const removedSegIds = [];
  431. let hnNavPointsToRemove = [],
  432. hnNavPointsNumbersToRemove = [];
  433. arrSegObjs.forEach(segObj => {
  434. const segmentId = segObj.getID();
  435. if (!eg.intersects(segObj.geometry) && (segmentId > 0)) {
  436. hnNavPointsToRemove = hnNavPointsToRemove.concat(_HNNavPointsLayer.getFeaturesByAttribute('segmentId', segmentId));
  437. if (!_settings.enableTooltip)
  438. hnNavPointsNumbersToRemove = hnNavPointsNumbersToRemove.concat(_HNNavPointsNumbersLayer.getFeaturesByAttribute('segmentId', segmentId));
  439. else
  440. removedSegIds.push(segmentId);
  441. const segIdx = findObjIndex(_processedSegments, 'segId', segmentId);
  442. if (segIdx > -1)
  443. _processedSegments.splice(segIdx, 1);
  444. }
  445. });
  446. if (hnNavPointsToRemove.length > 0)
  447. _HNNavPointsLayer.removeFeatures(hnNavPointsToRemove);
  448. if (hnNavPointsNumbersToRemove.length > 0)
  449. _HNNavPointsNumbersLayer.removeFeatures(hnNavPointsNumbersToRemove);
  450. if (removedSegIds.length > 0) {
  451. _HNNavPointsNumbersLayer.markers.filter(marker => removedSegIds.includes(marker.segmentId)).forEach(marker => {
  452. _HNNavPointsNumbersLayer.removeMarker(marker);
  453. });
  454. }
  455. }
  456. }
  457. else { // action = 'objectsadded', 'zoomend', 'init', 'exithousenumbers', 'hnLayerToggled', 'hnNumbersLayerToggled', 'settingChanged', 'afterSave'
  458. let i = arrSegObjs.length;
  459. while (i--) {
  460. if (arrSegObjs[i].getID() < 0) {
  461. arrSegObjs.splice(i, 1);
  462. }
  463. else {
  464. const segIdx = findObjIndex(_processedSegments, 'segId', arrSegObjs[i].getID());
  465. if (segIdx > -1) {
  466. if (arrSegObjs[i].getUpdatedOn() > _processedSegments[segIdx].updatedOn)
  467. _processedSegments[segIdx].updatedOn = arrSegObjs[i].getUpdatedOn();
  468. else if (!processAll)
  469. arrSegObjs.splice(i, 1);
  470. }
  471. else {
  472. _processedSegments.push({ segId: arrSegObjs[i].getID(), updatedOn: arrSegObjs[i].getUpdatedOn() });
  473. }
  474. }
  475. }
  476. while (arrSegObjs.length > 0) {
  477. let chunk;
  478. if (retry === 1)
  479. chunk = arrSegObjs.splice(0, 250);
  480. else if (retry === 2)
  481. chunk = arrSegObjs.splice(0, 125);
  482. else if (retry === 3)
  483. chunk = arrSegObjs.splice(0, 100);
  484. else if (retry === 4)
  485. chunk = arrSegObjs.splice(0, 50);
  486. else
  487. chunk = arrSegObjs.splice(0, 500);
  488. try {
  489. W.controller.descartesClient.getHouseNumbers(chunk.map(segObj => segObj.getID())).then(processJSON).catch(error => processError(error, [...chunk]));
  490. }
  491. catch (error) {
  492. processError(error, [...chunk]);
  493. }
  494. }
  495. }
  496. doSpinner('processSegs', false);
  497. }
  498.  
  499. function preventProcess() {
  500. if (!_settings.hnLines && !_settings.hnNumbers) {
  501. if (_scriptActive)
  502. initBackgroundTasks('disable');
  503. destroyAllHNs();
  504. return true;
  505. }
  506. if (W.map.getZoom() < _settings.disableBelowZoom) {
  507. destroyAllHNs();
  508. return true;
  509. }
  510. return false;
  511. }
  512.  
  513. function markerEvent(evt) {
  514. if (!evt || preventProcess())
  515. return;
  516. if (evt.type === 'click:input') {
  517. if (evt.object && evt.object.dragging && !evt.object.dragging.last)
  518. removeHNs([evt.object.model]);
  519. }
  520. else if (evt.type === 'delete') {
  521. removeHNs([evt.object.model]);
  522. }
  523. }
  524.  
  525. function setMarkersEvents() {
  526. if (W.editingMediator.attributes.editingHouseNumbers) {
  527. checkTimeout({ timeout: 'setMarkersEvents' });
  528. hideTooltip();
  529. if (!_wmeHnLayer || (_wmeHnLayer && (_wmeHnLayer.markers.length === 0))) {
  530. _timeouts.setMarkersEvents = window.setTimeout(setMarkersEvents, 50);
  531. return;
  532. }
  533. _wmeHnLayer.markers.forEach(marker => {
  534. marker.events.unregister('click:input', null, markerEvent);
  535. marker.events.unregister('delete', null, markerEvent);
  536. marker.events.on({ 'click:input': markerEvent, delete: markerEvent });
  537. });
  538. }
  539. else if (_wmeHnLayer) {
  540. _wmeHnLayer.markers.forEach(marker => {
  541. marker.events.unregister('click:input', null, markerEvent);
  542. marker.events.unregister('delete', null, markerEvent);
  543. });
  544. }
  545. }
  546.  
  547. function checkMarkersEvents() {
  548. if (_wmeHnLayer && (_wmeHnLayer.markers.length > 0) && !_wmeHnLayer.markers[0].events.listeners['click:input'].some(callbackFn => callbackFn.func === markerEvent))
  549. setMarkersEvents();
  550. }
  551.  
  552. function segmentsEvent(evt) {
  553. if (!evt || preventProcess())
  554. return;
  555. if ((this.action === 'objectssynced') || (this.action === 'objectsremoved'))
  556. processSegmentsToRemove();
  557. if (this.action === 'objectschanged-id') {
  558. const oldSegmentId = evt.oldID,
  559. newSegmentID = evt.newID;
  560. _HNNavPointsLayer.getFeaturesByAttribute('segmentId', oldSegmentId).forEach(feature => { feature.attributes.segmentId = newSegmentID; });
  561. if (_settings.enableTooltip)
  562. _HNNavPointsNumbersLayer.markers.filter(marker => marker.segmentId === oldSegmentId).forEach(marker => { marker.segmentId = newSegmentID; });
  563. else
  564. _HNNavPointsNumbersLayer.getFeaturesByAttribute('segmentId', oldSegmentId).forEach(feature => { feature.attributes.segmentId = newSegmentID; });
  565. }
  566. else if (this.action === 'objects-state-deleted') {
  567. evt.forEach(obj => {
  568. if (_segmentsToRemove.indexOf(obj.getID()) === -1)
  569. _segmentsToRemove.push(obj.getID());
  570. });
  571. }
  572. else {
  573. processSegs(this.action, evt.filter(seg => seg.attributes.hasHNs));
  574. }
  575. }
  576.  
  577. function objectsChangedIdHNs(evt) {
  578. if (!evt || preventProcess())
  579. return;
  580. const oldFeatureId = evt.oldID,
  581. newFeatureId = evt.newID;
  582. _HNNavPointsLayer.getFeaturesByAttribute('featureId', oldFeatureId).forEach(feature => { feature.attributes.featureId = newFeatureId; });
  583. if (_settings.enableTooltip)
  584. _HNNavPointsNumbersLayer.markers.filter(marker => marker.featureId === oldFeatureId).forEach(marker => { marker.featureId = newFeatureId; });
  585. else
  586. _HNNavPointsNumbersLayer.getFeaturesByAttribute('featureId', oldFeatureId).forEach(feature => { feature.attributes.featureId = newFeatureId; });
  587. }
  588.  
  589. function objectsChangedHNs(evt) {
  590. if (!evt || preventProcess())
  591. return;
  592. if ((evt.length === 1) && evt[0].getSegmentId() && (_segmentsToProcess.indexOf(evt[0].getSegmentId()) === -1))
  593. _segmentsToProcess.push(evt[0].getSegmentId());
  594. checkMarkersEvents();
  595. }
  596.  
  597. function objectsStateDeletedHNs(evt) {
  598. if (!evt || preventProcess())
  599. return;
  600. if ((evt.length === 1) && evt[0].getSegmentId() && (_segmentsToProcess.indexOf(evt[0].getSegmentId()) === -1))
  601. _segmentsToProcess.push(evt[0].getSegmentId());
  602. removeHNs(evt);
  603. checkMarkersEvents();
  604. }
  605.  
  606. function objectsAddedHNs(evt) {
  607. if (!evt || preventProcess())
  608. return;
  609. if ((evt.length === 1) && evt[0].getSegmentId() && (_segmentsToProcess.indexOf(evt[0].getSegmentId()) === -1))
  610. _segmentsToProcess.push(evt[0].getSegmentId());
  611. checkMarkersEvents();
  612. }
  613.  
  614. function zoomEndEvent() {
  615. if (preventProcess())
  616. return;
  617. if ((W.map.getZoom() < _settings.disableBelowZoom))
  618. destroyAllHNs();
  619. if ((W.map.getZoom() > (_settings.disableBelowZoom - 1)) && (_processedSegments.length === 0))
  620. processSegs('zoomend', W.model.segments.getByAttributes({ hasHNs: true }), true);
  621. }
  622.  
  623. function afterActionsEvent(evt) {
  624. if (!evt || preventProcess())
  625. return;
  626. if ((evt.type === 'afterclearactions') || (evt.type === 'noActions')) {
  627. processSegmentsToRemove();
  628. }
  629. else if (evt.action._description && (evt.action._description.indexOf('Deleted house number') > -1)) {
  630. if (evt.type === 'afterundoaction')
  631. drawHNs([evt.action.object]);
  632. else
  633. removeHNs(evt.action.object);
  634. setMarkersEvents();
  635. }
  636. else if (evt.action._description && (evt.action._description.indexOf('Updated house number') > -1)) {
  637. const tempEvt = _.cloneDeep(evt);
  638. if (evt.type === 'afterundoaction') {
  639. if (tempEvt.action.newAttributes && tempEvt.action.newAttributes.number)
  640. tempEvt.action.attributes.number = tempEvt.action.newAttributes.number;
  641. }
  642. else if (evt.type === 'afteraction') {
  643. if (tempEvt.action.oldAttributes && tempEvt.action.oldAttributes.number)
  644. tempEvt.action.attributes.number = tempEvt.action.oldAttributes.number;
  645. }
  646. removeHNs(tempEvt.action.object);
  647. drawHNs([evt.action.object]);
  648. setMarkersEvents();
  649. }
  650. else if (evt.action._description && (evt.action._description.indexOf('Added house number') > -1)) {
  651. if (evt.type === 'afterundoaction')
  652. removeHNs(evt.action.houseNumber);
  653. else
  654. drawHNs([evt.action.houseNumber]);
  655. }
  656. else if (evt.action._description && (evt.action._description.indexOf('Moved house number') > -1)) {
  657. drawHNs([evt.action.newHouseNumber]);
  658. }
  659. else if (evt.action && evt.action.houseNumber) {
  660. drawHNs((evt.action.newHouseNumber ? [evt.action.newHouseNumber] : [evt.action.houseNumber]));
  661. setMarkersEvents();
  662. }
  663. checkMarkersEvents();
  664. }
  665.  
  666. async function reloadClicked() {
  667. if (preventProcess() || ($('div.item-icon.w-icon.w-icon-refresh').attr('class').indexOf('disabled') > 0))
  668. return;
  669. await destroyAllHNs();
  670. processSegs('reload', W.model.segments.getByAttributes({ hasHNs: true }));
  671. }
  672.  
  673. function initBackgroundTasks(status) {
  674. if (status === 'enable') {
  675. _HNLayerObserver = new MutationObserver(mutationsList => {
  676. mutationsList.forEach(() => {
  677. const input = $('div.olLayerDiv.house-numbers-layer div.house-number div.content.active:not(".new") input.number');
  678. if (input.val() === '')
  679. input[0].addEventListener('change', setMarkersEvents);
  680. });
  681. });
  682. _saveButtonObserver = new MutationObserver(mutationsList => {
  683. if (mutationsList.filter(
  684. mutation => (mutation.attributeName === 'class')
  685. && (mutation.target.classList.contains('waze-icon-save'))
  686. && (mutation.oldValue.indexOf('ItemDisabled') === -1)
  687. && (mutation.target.classList.contains('ItemDisabled'))
  688. ).length > 0) {
  689. if (W.editingMediator.attributes.editingHouseNumbers)
  690. processSegs('afterSave', W.model.segments.getByIds(_segmentsToProcess), true);
  691. else
  692. processSegmentsToRemove();
  693. }
  694. });
  695. _saveButtonObserver.observe($('#edit-buttons .waze-icon-save')[0], {
  696. childList: false, attributes: true, attributeOldValue: true, characterData: false, characterDataOldValue: false, subtree: false
  697. });
  698. _saveButtonObserver.observing = true;
  699. W.accelerators.events.on({ reloadData: destroyAllHNs });
  700. $('#overlay-buttons, #edit-buttons').on('click', 'div.reload-button-region', reloadClicked);
  701. W.model.segments.on('objectsadded', segmentsEvent, { action: 'objectsadded' });
  702. W.model.segments.on('objectsremoved', segmentsEvent, { action: 'objectsremoved' });
  703. W.model.segments.on('objectssynced', segmentsEvent, { action: 'objectssynced' });
  704. W.model.segments.on('objects-state-deleted', segmentsEvent, { action: 'objects-state-deleted' });
  705. W.model.segments.on('objectschanged-id', segmentsEvent, { action: 'objectschanged-id' });
  706. W.model.segmentHouseNumbers.on({
  707. objectsadded: objectsAddedHNs,
  708. objectschanged: objectsChangedHNs,
  709. 'objectschanged-id': objectsChangedIdHNs,
  710. 'objects-state-deleted': objectsStateDeletedHNs
  711. });
  712. W.editingMediator.on({ 'change:editingHouseNumbers': observeHNLayer });
  713. W.map.events.on({
  714. zoomend: zoomEndEvent, addlayer: checkLayerIndex, removelayer: checkLayerIndex
  715. });
  716. WazeWrap.Events.register('afterundoaction', this, afterActionsEvent);
  717. WazeWrap.Events.register('afteraction', this, afterActionsEvent);
  718. WazeWrap.Events.register('afterclearactions', this, afterActionsEvent);
  719. /* 2020.07.16.01 - See note at top
  720. _hnMouseoverCtrl.activate();
  721. */
  722. _scriptActive = true;
  723. }
  724. else if (status === 'disable') {
  725. _HNLayerObserver = undefined;
  726. _saveButtonObserver = undefined;
  727. W.accelerators.events.on('reloadData', null, destroyAllHNs);
  728. $('#overlay-buttons, #edit-buttons').off('click', 'div.reload-button-region', reloadClicked);
  729. W.model.segments.off('objectsadded', segmentsEvent, { action: 'objectsadded' });
  730. W.model.segments.off('objectsremoved', segmentsEvent, { action: 'objectsremoved' });
  731. W.model.segments.off('objectschanged', segmentsEvent, { action: 'objectschanged' });
  732. W.model.segments.off('objects-state-deleted', segmentsEvent, { action: 'objects-state-deleted' });
  733. W.model.segments.off('objectschanged-id', segmentsEvent, { action: 'objectschanged-id' });
  734. W.model.segmentHouseNumbers.off({
  735. objectsadded: objectsAddedHNs,
  736. objectschanged: objectsChangedHNs,
  737. 'objectschanged-id': objectsChangedIdHNs,
  738. 'objects-state-deleted': objectsStateDeletedHNs,
  739. objectsremoved: removeHNs
  740. });
  741. W.editingMediator.off({ 'change:editingHouseNumbers': observeHNLayer });
  742. W.map.events.unregister('zoomend', null, zoomEndEvent);
  743. W.map.events.unregister('addlayer', null, checkLayerIndex);
  744. W.map.events.unregister('removelayer', null, checkLayerIndex);
  745. WazeWrap.Events.unregister('afterundoaction', this, afterActionsEvent);
  746. WazeWrap.Events.unregister('afteraction', this, afterActionsEvent);
  747. /* 2020.07.16.01 - See note at top
  748. _hnMouseoverCtrl.deactivate();
  749. */
  750. _scriptActive = false;
  751. }
  752. return Promise.resolve();
  753. }
  754.  
  755. function enterHNEditMode(evt) {
  756. if (evt && evt.data && evt.data.segment) {
  757. if (evt.data.moveMap)
  758. W.map.setCenter(new OpenLayers.LonLat(evt.data.segment.getCenter().x, evt.data.segment.getCenter().y), W.map.getZoom());
  759. W.selectionManager.setSelectedModels(evt.data.segment);
  760. $('#segment-edit-general .edit-house-numbers').click();
  761. }
  762. }
  763.  
  764. function showTooltip(evt) {
  765. if ((W.map.getZoom() < 6) || W.editingMediator.attributes.editingHouseNumbers || !_settings.enableTooltip)
  766. return;
  767. if (evt && evt.object && evt.object.featureId) {
  768. /* 2020.07.16.01 - See note at top
  769. if (evt && evt.feature && evt.feature.attributes && evt.feature.attributes.featureId) {
  770. */
  771. checkTooltip();
  772. /* 2020.07.16.01 - See note at top
  773. const featureArr = evt.feature.attributes.featureId.split('|'),
  774. */
  775. const { segmentId, hnNumber } = evt.object;
  776. if (_popup.inUse && (_popup.hnNumber === hnNumber) && (_popup.segmentId === segmentId))
  777. return;
  778. const segment = W.model.segments.getObjectById(segmentId),
  779. street = W.model.streets.getObjectById(segment.attributes.primaryStreetID),
  780. popupPixel = W.map.getPixelFromLonLat(evt.object.lonlat),
  781. /* 2020.07.16.01 - See note at top
  782. popupPixel = W.map.getPixelFromLonLat(new OpenLayers.LonLat(evt.feature.geometry.getCentroid().x, evt.feature.geometry.getCentroid().y)),
  783. */
  784. htmlOut = ''
  785. + '<div class="tippy-tooltip light-border-theme" id="hnNavPointsTooltipDiv-tooltip" data-size="large" data-animation="shift-away" data-state="visible"'
  786. + ' data-interactive="" style="transition-duration:325ms; top:0px;">'
  787. + ' <div class="tippy-arrow" id="hnNavPointsTooltipDiv-arrow" style="left:83px;"></div>'
  788. + ' <div class="tippy-content" id="hnNavPointsTooltipDiv-content" data-state="visible" style="transition-duration: 325ms;">'
  789. + ' <div>'
  790. + ' <div class="house-number-marker-tooltip">'
  791. + ` <div class="title" dir="auto">${hnNumber} ${(street ? street.name : '')}</div>`
  792. + ` <div class="edit-button fa fa-pencil" id="hnNavPointsTooltipDiv-edit" ${(segment.canEditHouseNumbers() ? '' : ' style="display:none"')}></div>`
  793. + ' </div>'
  794. + ' </div>'
  795. + ' </div>'
  796. + '</div>';
  797. _$hnNavPointsTooltipDiv.html(htmlOut);
  798. popupPixel.origX = popupPixel.x;
  799. const popupWidthHalf = (_$hnNavPointsTooltipDiv.width() / 2);
  800. let arrowOffset = (popupWidthHalf - 15),
  801. xPlacement = 'top',
  802. moveMap = false;
  803. popupPixel.x = ((popupPixel.x - popupWidthHalf) > 0) ? (popupPixel.x - popupWidthHalf) : 10;
  804. if (popupPixel.x === 10)
  805. arrowOffset = popupPixel.origX - 22;
  806. if ((popupPixel.x + (popupWidthHalf * 2)) > $('#map')[0].clientWidth) {
  807. popupPixel.x = (popupPixel.origX - _$hnNavPointsTooltipDiv.width() + 8);
  808. arrowOffset = (_$hnNavPointsTooltipDiv.width() - 30);
  809. moveMap = true;
  810. }
  811. if (popupPixel.y - _$hnNavPointsTooltipDiv.height() < 0) {
  812. popupPixel.y += 10;
  813. xPlacement = 'bottom';
  814. }
  815. else {
  816. popupPixel.y -= (_$hnNavPointsTooltipDiv.height() + 4);
  817. }
  818. $('#hnNavPointsTooltipDiv-edit').on('click', { segment, moveMap }, enterHNEditMode);
  819. _$hnNavPointsTooltipDiv.css({ transform: `translate3d(${Math.round(popupPixel.x)}px, ${Math.round(popupPixel.y)}px, 0px)` });
  820. $('#hnNavPointsTooltipDiv-arrow').css('left', Math.round(arrowOffset));
  821. _$hnNavPointsTooltipDiv.attr('x-placement', xPlacement);
  822. _$hnNavPointsTooltipDiv.css({ visibility: 'visible' });
  823. _popup = { segmentId, hn_number: hnNumber, inUse: true };
  824. }
  825. }
  826.  
  827. function hideTooltip() {
  828. checkTimeout({ timeout: 'hideTooltip' });
  829. _$hnNavPointsTooltipDiv.css({ visibility: 'hidden' });
  830. _$hnNavPointsTooltipDiv.html('');
  831. _popup = { segmentId: -1, hnNumber: -1, inUse: false };
  832. }
  833.  
  834. function hideTooltipDelay(evt) {
  835. if (!evt)
  836. return;
  837. checkTimeout({ timeout: 'hideTooltip' });
  838. const parentsArr = (evt.toElement && evt.toElement.offsetParent) ? [evt.toElement.offsetParent, evt.toElement.offsetParent.offSetParent] : [];
  839. if (evt.toElement && ((parentsArr.indexOf(_HNNavPointsNumbersLayer.div) > -1) || (parentsArr.indexOf(_$hnNavPointsTooltipDiv[0]) > -1)))
  840. return;
  841. _timeouts.hideTooltip = window.setTimeout(hideTooltip, 100, evt);
  842. }
  843.  
  844. function checkTooltip() {
  845. checkTimeout({ timeout: 'hideTooltip' });
  846. }
  847.  
  848. function checkLayerIndex() {
  849. const layerIdx = W.map.layers.map(a => a.uniqueName).indexOf('__HNNavPointsNumbersLayer');
  850. let properIdx;
  851. if (_settings.keepHNLayerOnTop) {
  852. const layersIndexes = [],
  853. layersLoaded = W.map.layers.map(a => a.uniqueName);
  854. ['wmeGISLayersDefault', '__HNNavPointsLayer'].forEach(layerUniqueName => {
  855. if (layersLoaded.indexOf(layerUniqueName) > 0)
  856. layersIndexes.push(layersLoaded.indexOf(layerUniqueName));
  857. });
  858. properIdx = (Math.max(...layersIndexes) + 1);
  859. }
  860. else {
  861. properIdx = (W.map.layers.map(a => a.uniqueName).indexOf('__HNNavPointsLayer') + 1);
  862. }
  863. if (layerIdx !== properIdx) {
  864. W.map.layers.splice(properIdx, 0, W.map.layers.splice(layerIdx, 1)[0]);
  865. W.map.getOLMap().resetLayersZIndex();
  866. }
  867. }
  868.  
  869. async function init() {
  870. const navPointsNumbersLayersOptions = {
  871. displayInLayerSwitcher: true,
  872. uniqueName: '__HNNavPointsNumbersLayer',
  873. selectable: true,
  874. labelSelect: true,
  875. rendererOptions: { zIndexing: true },
  876. styleMap: new OpenLayers.StyleMap({
  877. default: new OpenLayers.Style({
  878. strokeColor: '${Color}',
  879. strokeOpacity: 1,
  880. strokeWidth: 3,
  881. fillColor: '${Color}',
  882. fillOpacity: 0.5,
  883. pointerEvents: 'visiblePainted',
  884. label: '${hn_number}',
  885. fontSize: '12px',
  886. fontFamily: 'Rubik, Boing-light, sans-serif;',
  887. fontWeight: 'bold',
  888. direction: '${textDir}',
  889. labelOutlineColor: '${Color}',
  890. labelOutlineWidth: 3,
  891. labelSelect: true
  892. })
  893. })
  894. };
  895. log('Initializing.');
  896. await loadSettingsFromStorage();
  897. WazeWrap.Interface.AddLayerCheckbox('display', 'HN NavPoints', _settings.hnLines, hnLayerToggled);
  898. WazeWrap.Interface.AddLayerCheckbox('display', 'HN NavPoints Numbers', _settings.hnNumbers, hnNumbersLayerToggled);
  899.  
  900. _HNNavPointsLayer = new OpenLayers.Layer.Vector('HN NavPoints Layer', {
  901. displayInLayerSwitcher: true,
  902. uniqueName: '__HNNavPointsLayer'
  903. });
  904. _HNNavPointsNumbersLayer = _settings.enableTooltip
  905. ? new OpenLayers.Layer.Markers('HN NavPoints Numbers Layer', navPointsNumbersLayersOptions)
  906. : new OpenLayers.Layer.Vector('HN NavPoints Numbers Layer', navPointsNumbersLayersOptions);
  907. W.map.addLayers([_HNNavPointsLayer, _HNNavPointsNumbersLayer]);
  908. _HNNavPointsLayer.setVisibility(_settings.hnLines);
  909. _HNNavPointsNumbersLayer.setVisibility(_settings.hnNumbers);
  910. /* 2020.07.16.01 - See note at top
  911. _hnMouseoverCtrl = new OpenLayers.Control.SelectFeature(_HNNavPointsNumbersLayer, {
  912. hover: true,
  913. highlightOnly: true,
  914. renderIntent: 'temporary',
  915. eventListeners: {
  916. featurehighlighted: showTooltip,
  917. featureunhighlighted: hideTooltipDelay
  918. }
  919. });
  920. W.map.addControl(_hnMouseoverCtrl);
  921. if (_settings.enableTooltip)
  922. _hnMouseoverCtrl.activate();
  923. */
  924. window.addEventListener('beforeunload', () => { checkShortcutsChanged(); }, false);
  925. new WazeWrap.Interface.Shortcut(
  926. 'toggleHNNavPointsShortcut',
  927. 'Toggle HN NavPoints layer',
  928. 'layers',
  929. 'layersToggleHNNavPoints',
  930. _settings.toggleHNNavPointsShortcut,
  931. () => { $('#layer-switcher-item_hn_navpoints').click(); },
  932. null
  933. ).add();
  934. new WazeWrap.Interface.Shortcut(
  935. 'toggleHNNavPointsNumbersShortcut',
  936. 'Toggle HN NavPoints Numbers layer',
  937. 'layers',
  938. 'layersToggleHNNavPointsNumbers',
  939. _settings.toggleHNNavPointsNumbersShortcut,
  940. () => { $('#layer-switcher-item_hn_navpoints_numbers').click(); },
  941. null
  942. ).add();
  943. $('#sidepanel-prefs').append(() => {
  944. let htmlOut = '<div style="border-bottom:1px solid black; padding-bottom:10px;';
  945. if ($('#sidepanel-prefs')[0].lastChild.tagName.search(/HR/gi) > -1) {
  946. const elmnt = $('#sidepanel-prefs')[0].lastChild;
  947. elmnt.style.borderTopColor = 'black';
  948. elmnt.style.color = 'black';
  949. }
  950. else {
  951. htmlOut += 'border-top:1px solid black;';
  952. }
  953. htmlOut += '"><h4>WME HN NavPoints</h4>'
  954. + '<div style="font-size:12px; margin-left:6px;">'
  955. + '<div style="margin-bottom:5px;" title="Disable NavPoints and house numbers when zoom level is less than specified number.\r\nMinimum: 4\r\nDefault: 5">'
  956. + `Disable when zoom level <<input type="text" id="HNNavPoints_disableBelowZoom" style="width:24px; height:20px; margin-left:4px;" value="${_settings.disableBelowZoom}"></input></div>`
  957. + `<input type="checkbox" style="margin-top:1px;" id="HNNavPoints_cbenableTooltip" title="Enable tooltip when mousing over house numbers."${(_settings.enableTooltip ? ' checked' : '')}>`
  958. + ' <label for="HNNavPoints_cbenableTooltip" style="font-weight:normal; vertical-align:top"'
  959. + ' title="Enable tooltip when mousing over house numbers.\r\nWarning: This may cause performance issues.">Enable tooltip</label><br>'
  960. + '<input type="checkbox" style="margin-top:1px;" id="HNNavPoints_cbkeepHNLayerOnTop" '
  961. + `title="Keep house numbers layer on top of all other layers."${(_settings.keepHNLayerOnTop ? ' checked' : '')}>`
  962. + ' <label for="HNNavPoints_cbenableTooltip" style="font-weight:normal; vertical-align:top" title="Keep house numbers layer on top of all other layers.">Keep HN layer on top</label>'
  963. + '</div>'
  964. + '<div style="margin:0 10px 0 10px; width:130px; text-align:center; font-size:12px; background:black; font-weight:600;">'
  965. + ' <div style="text-shadow:0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white;">Touched</div>'
  966. + ' <div style="text-shadow:0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange;'
  967. + ' ">Touched forced</div>'
  968. + ' <div style="text-shadow:0 0 3px yellow,0 0 3px yellow,0 0 3px yellow, 0 0 3px yellow,0 0 3px yellow,0 0 3px yellow,0 0 3px yellow,0 0 3px yellow,0 0 3px yellow,0 0 3px yellow;'
  969. + ' ">Untouched</div>'
  970. + ' <div style="text-shadow:0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red;">Untouched forced</div>'
  971. + '</div></div>';
  972. return htmlOut;
  973. });
  974. $('#HNNavPoints_disableBelowZoom').on('change', function () {
  975. const newVal = Math.min(10, Math.max(4, parseInt(this.value)));
  976. if (newVal !== _settings.disableBelowZoom) {
  977. if (newVal !== parseInt(this.value))
  978. this.value = newVal;
  979. _settings.disableBelowZoom = newVal;
  980. saveSettingsToStorage();
  981. if ((W.map.getZoom() < newVal) && (_settings.hnLines || _settings.hnNumbers))
  982. processSegs('settingChanged', null, true, 0);
  983. else if (_settings.hnLines || _settings.hnNumbers)
  984. processSegs('settingChanged', W.model.segments.getByAttributes({ hasHNs: true }), true, 0);
  985. }
  986. });
  987. $('input[id^="HNNavPoints_cb"]').off().on('click', function () {
  988. const settingName = $(this)[0].id.substr(14);
  989. if (settingName === 'enableTooltip') {
  990. if (!this.checked)
  991. _HNNavPointsNumbersLayer.clearMarkers();
  992. else
  993. _HNNavPointsNumbersLayer.destroyFeatures();
  994. W.map.removeLayer(_HNNavPointsNumbersLayer);
  995. if (this.checked)
  996. _HNNavPointsNumbersLayer = new OpenLayers.Layer.Markers('HN NavPoints Numbers Layer', navPointsNumbersLayersOptions);
  997. else
  998. _HNNavPointsNumbersLayer = new OpenLayers.Layer.Vector('HN NavPoints Numbers Layer', navPointsNumbersLayersOptions);
  999. W.map.addLayer(_HNNavPointsNumbersLayer);
  1000. _HNNavPointsNumbersLayer.setVisibility(_settings.hnNumbers);
  1001. }
  1002. _settings[settingName] = this.checked;
  1003. if (settingName === 'keepHNLayerOnTop')
  1004. checkLayerIndex();
  1005. saveSettingsToStorage();
  1006. if ((settingName === 'enableTooltip') && (W.map.getZoom() > (_settings.disableBelowZoom - 1)) && (_settings.hnLines || _settings.hnNumbers))
  1007. processSegs('settingChanged', W.model.segments.getByAttributes({ hasHNs: true }), true, 0);
  1008. });
  1009. if (!_$hnNavPointsTooltipDiv) {
  1010. $('#map').append(
  1011. '<div id="hnNavPointsTooltipDiv" class="tippy-popper" role="tooltip" x-placement="top" style="z-index:9999; transition-duration:0ms; position:absolute;'
  1012. + 'will-change:transform; top:0px; left:0px; visibility:none;"></div>'
  1013. );
  1014. _$hnNavPointsTooltipDiv = $('#hnNavPointsTooltipDiv');
  1015. _$hnNavPointsTooltipDiv.on('mouseleave', null, hideTooltipDelay);
  1016. _$hnNavPointsTooltipDiv.on('mouseenter', null, checkTooltip);
  1017. }
  1018. await initBackgroundTasks('enable');
  1019. checkLayerIndex();
  1020. log(`Fully initialized in ${Math.round(performance.now() - LOAD_BEGIN_TIME)} ms.`);
  1021. showScriptInfoAlert();
  1022. if (_scriptActive)
  1023. processSegs('init', W.model.segments.getByAttributes({ hasHNs: true }));
  1024. setTimeout(checkShortcutsChanged, 10000);
  1025. }
  1026.  
  1027. function bootstrap(tries) {
  1028. if (W && W.map && W.model && $ && WazeWrap.Ready) {
  1029. checkTimeout({ timeout: 'bootstrap' });
  1030. log('Bootstrapping.');
  1031. init();
  1032. }
  1033. else if (tries < 1000) {
  1034. logDebug(`Bootstrap failed. Retrying ${tries} of 1000`);
  1035. _timeouts.bootstrap = window.setTimeout(bootstrap, 200, ++tries);
  1036. }
  1037. else {
  1038. logError('Bootstrap timed out waiting for WME to become ready.');
  1039. }
  1040. }
  1041.  
  1042. bootstrap(1);