WME HN NavPoints

Shows navigation points of all house numbers in WME

目前为 2023-07-20 提交的版本。查看 最新版本

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