UROverview Plus (URO+)

Adds a whole bunch of features to WME, which someday I may get around to documenting properly...

当前为 2024-11-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name UROverview Plus (URO+)
  3. // @namespace http://greasemonkey.chizzum.com
  4. // @description Adds a whole bunch of features to WME, which someday I may get around to documenting properly...
  5. // @include https://*.waze.com/*editor*
  6. // @exclude https://editor-beta.waze.com/*
  7. // @exclude https://beta.waze.com/*
  8. // @exclude https://www.waze.com/user/*editor/*
  9. // @exclude https://www.waze.com/*/user/*editor/*
  10. // @grant none
  11. // @version 4.10
  12. // ==/UserScript==
  13.  
  14. /*
  15. =======================================================================================================================
  16. Bug fixes - MUST BE CLEARED BEFORE RELEASE
  17. =======================================================================================================================
  18.  
  19. =======================================================================================================================
  20. Things to be checked
  21. =======================================================================================================================
  22.  
  23. */
  24.  
  25. /* JSHint Directives */
  26. /* globals $: */
  27. /* globals W: true */
  28. /* globals I18n: */
  29. /* globals OpenLayers: true */
  30. /* globals require: */
  31. /* globals ResizeObserver: */
  32. /* globals _: */
  33. /* globals trustedTypes: */
  34. /* jshint bitwise: false */
  35. /* jshint eqnull: true */
  36. /* jshint esversion: 11 */
  37. /* jshint undef: true */
  38. /* jshint unused: true */
  39.  
  40. const uroRelease =
  41. {
  42. version : "4.10",
  43. date : "20241127",
  44. changes :
  45. [
  46. "Compatibility updates"
  47. ]
  48. };
  49.  
  50. const uroEnums =
  51. {
  52. FP_OPTS:
  53. {
  54. filterUneditable: 0,
  55. filterInsideManagedAreas: 1,
  56. excludeMyAreas: 2,
  57. filterLockRanked: 3,
  58. filterFlagged: 4,
  59. filterNewPlace: 5,
  60. filterUpdatedDetails: 6,
  61. filterNewPhoto: 7,
  62. filterMinPURAge: 8,
  63. filterMaxPURAge: 9,
  64. invertPURFilters: 10,
  65. filterHighSeverity: 11,
  66. filterMedSeverity: 12,
  67. filterLowSeverity: 13,
  68. leavePURGeos: 14,
  69. filterCFPhone: 15,
  70. filterCFName: 16,
  71. filterCFEntryExitPoints: 17,
  72. filterCFOpeningHours: 18,
  73. filterCFAliases: 19,
  74. filterCFServices: 20,
  75. filterCFGeometry: 21,
  76. filterCFHouseNumber: 22,
  77. filterCFCategories: 23,
  78. filterCFDescription: 24,
  79. filterOnCFs: 25,
  80. thresholdMinPURDays: 26,
  81. thresholdMaxPURDays: 27,
  82. isLoggedIn: 28,
  83. userRank: 29,
  84. N_OPTS: 30
  85. },
  86. TRTC:
  87. {
  88. UNKNOWN: 0,
  89. WME: 1,
  90. WAZEFEED: 2,
  91. WAZEOTHER: 3
  92. },
  93. SRTC:
  94. {
  95. UNKNOWN: 0,
  96. EXPIRED: 1,
  97. ACTIVE: 2,
  98. FUTURE: 3
  99. },
  100. DRTC:
  101. {
  102. NONE: 0,
  103. SEG_AB: 1,
  104. SEG_BA: 2,
  105. SEG_BI: 4,
  106. TURN_OUT: 8,
  107. TURN_IN: 16
  108. }
  109. };
  110. const uroCustomURTags = ['[ROADWORKS]','[CONSTRUCTION]','[CLOSURE]','[EVENT]','[NOTE]','[WSLM]','[BOG]','[DIFFICULT]'];
  111. const uroImages =
  112. {
  113. HighlightedCameraImages :
  114. [
  115. // speed
  116. ["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEoAAABICAYAAABRGGN6AAAgH3pUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHja1Ztpchw5c4b/1yl8BACJ9ThYI3wDH9/Pi2pRombmW8YOR1gKiWSxuwpAZr5LAv3s//rP8/wHf0pz+Ymp1NxydvyJLbbQ+aa698/71bt4/79/Yv/8zn+//sQfbwpcMr7a+2Pen9d3rqefbyjxc318v/6U+blP/dzIf934/jE9Wd+PzyA/N7LwXvefn5/2eV+Pv0zn8y/Mz20/N//951hYjJW4n4UnbGv8q+9TjBFYtc5Xe/8PupL5PvJVV/5i7Z6vb39bvNP+fO1c/7zCvi/F4/LnBfm3Nfpc9+m36/YVtfBtRP7Ht+H7L+bwy/3655e1O2fVc/Y7ux4zK5Wfz6T8O4R3GLyQGEW7b8v8LfxLfF/u38bfyhQnEVtuEs/h5uObD97c8dEv3/3x+36dfjLEGHYofA1hBrvXqpXQwrxBifrrTyjWbD3EKNgkbsbl8DUWf5/b7vOmrzx5eV4ZPDfzvOMPf58/u/h3/n7d6ByluPd3MdtdcsYVlNMMQ5HT/7yKgPjziVv6rC9/n6+w/vyjwBoRTHeZKxPsbry3GMn/zC27cTZel1x83FsavqzPDVginp0YjDci4LK35LN3JYTiPetYiU9n5MFiGETApxSWfw6xMcsEpwY9m/cUf18bUngvAy0EIlEohdA06wQrxkT+lFjJoZ4sxSellFNJNbXUs+WYU865ZGFUL1ZiSSWXUmpppVersaaaa6m1ttpbaAaEpZZbeVptrfXOQzu37ry784reRxg24kgjjzLqaKNP0mfGmWaeZdbZZl9h2aL8V17lWXW11bffpNKOO+28y6677X7ItWMnnnTyKaeedvpX1D5R/R41/1vk/nHU/Cdqili8rys/o8blUn7cwgtOkmJGxEL0RLwoAiR0UMxc9TEGRU4xcy1QFCkQNZ8UnOUVMSIYtw/p+K/Y/YzcP4zbk+K/FbfwV5F7FLr/jcg9Ct0ncn+M259EbYnX5o3YW4VaU2fnUYmeXXuoXZz0t78+/9Mb/H+90TqCsJRry2sHO54s2c1KPaFHb6e10bpXHaxn+GGgcnB1dzK4n0Q11BTS9kO3IRKLS/vsWfTzzHsA8SvvvTPfpMCXvcOTRmnLbGQycnYLO2a3uWssLtZ+cj3VfB/Kra4b5TtK0rUbWdp2Oo2h7PV4Uq6NYpts9qO2mQtZEfppax6KZ7cIMuS60k51goU292GEIa84WhrDYup23JNsGmO2M94nrsuiZFddK5QzQ0vHbb45e/c72XKy6eu2knYLufU8diSzqc79rkaf3KbNMvy+M+jpTGv63ec571P4udae74qFVnkOGPPoQbz2fcwOYawQV8y5IwePntAOM9pzG/eg8I/LY9r4zKGcHzN4jm7Oi62AknrbXMl+BGmiMDtrnVnu1AYocqhDCJ5p8IK27tzXcac8mnz+PqZJgMvYvJnnU/nDpbrqHDabjy3zjSfMLixEAINzHiQgIcfop/If6GGaOLdbNWjGq8zJDAoBRrllDaHUud0xAOTocS0AEPtAfO2pERgpxdaKANCINk/wJTvr20IZhchZP73N1q2OPohMCJ0cnWkBMY2hhl5Cf5bXAvuYfDrR5ajvYpJa/fe+Pr9cyCM35BGPO+lNCPK4F9YP7tbPqdYxQXKmW6sqJw9y5Mxct7FGHZwmjGVFFpsBZ5/MJ3jCRoqtJxYr+F5BW1aVga/2ZlCvxw/eiG44JzwZlUasqOmzeTsJTYirewvWkrtZrqonDC3zI1keb/k6yGZn0LmVbPVZ1PvNVd9C0MgGf1cqRq7MHDf3Lz2VoBd1TeOOh9Vk9K3Xe0uCsJ9NwsxCPiYGSbayGCNuuII8PlP4f8ryA37apNxaDKGTanPu4ltNnVQBPWp6Uh15r2FpLV8YAGKG+8BHQEHVcMY7EfQTCf7Ha6RnL2vdzA538WBAFsjcbqfcydpkoYji2SYA6I1gNAAS5EhxTfTssQWCnLErTOt+vwQuMdwN5JDI3GSaFqYKDcvXNaVk4MnovsVKZRCSm7S9vCCCiOuS+/UKOLyAzjtmDZ2Bpx+Xfs4F8fd0IryzsoOnHURDZP4s9gLDBRxHqkG3BsWQeHsiMFyZofS1lUVNELZyejbAC85CCGesCKmXTGBQI7tmkFdEAACGm+ndEdMd57xrOFMqC6z3tU9LT5Ax8KGDEJRGATSGQfsxAchELxQgKcUiTqnDlST8PWidoDTuvXLPimBZz0I+ZcEBoDKQOBk9NKmW6Ta13MeCMKiGuFbHfRwumW0G2X1tt0y1qrDgwz+nb/61r3uMST44uyXkc0/LErmU61MD8c/AyymkD/UB4iyB8Rjkane7spqjlHkgWzAOBgxCuaV8QppnQ9ftkZ5zAaLKMg2jFohXDiMfymkEgBVr1nKsG+mYJSzhSuttSzUlhC/Ody6qxp5YIMLaNkmHeqSG6onxiO9Tdywq2gy5uMYcdcYVsGtQ8UEJlMHySOaBKHW6pze3DLjdxVLBdnS0HqoP9iUjFksD2tslqLaV4DCr42eS0jbU2IEjz1cZP90noZyF/sTFIlmRNdf76nniZSNJar3yx+s9cgKkYqWJMTnyDLeYUqkdMQKkIm0bGmBVEnlgGzMqlfus3VYCLhC3xkNWyOXSYmXODCm38WSyqzOaifrggo+5Yq9n3+RvHcCKUSHJrzNSaaxghSUpV4UKVcCa74TqLtxoL5dgVA/7EZAwMI1yDhCUW4UamTEsLACj4cYkhGQ81g3BHxOerVbyGxZ8ZrFZkMo2JJN3XKgw8m411QXkOHHXhyXJi2elv+at5y+JSkiNPSToTDDCh68uaa6MJiQfnRIDaHBW3Bub5ci/XdZhyHphgITI4HXBQzAPV8Fy6PhxDBgokpALoRZUql4Vy0xbBbMBqkFIWUyQqrbhtqp4sCYemRYG2r42n8l8sAnhzypX5lz6JjnPdUD5UP1zTwpmTFiTQa4Uh2spwBSYWlQChWM3A4gOWRUHz/JoRvACTWCDtcZoACO4FFPdsqa94vI2Ngz470lJuVvB7nEfq/wH6yojE1Un6qXG5n1Ej/z4KO3f5CfbnaoBx3ZfjxNTMiu79T+lR7qBErYDFeOK3rHA4HwSuvLZVkmqmvUSUTcBx2LlizdIyPaBWOCy5wR+DN8oDJQNuD0PZSaoGicij9u/4hDkDnGP0AVZx62bfOJi3fMyf3rMTM0QBxvUb5OgQqdTOAJkIROVP7Adac5o66wk6sYATlJeTBIyfLMIWlrI4wLQd6AGbV69MBCdyC9R7vEUdN5OkMBV4KOw6gVIoeIPJHC5xCMsrqqlCjGUpZIUtcO0CKiOOIIfEKzUEEEiilQ20Mo7UDoNmCHNmsNFn7Agvx4Qo/BqgtbjlGbsE4pa0QtDAeUEV8if1I01jRLIo4U0dgPL2vCeuTrg3hD3sqLQeJtkwyIiME4pI5BwKA+MTXDNL6wLL4HZAI7NnMAuWMhUdIhWaG/xu2dgsrtv8CW34XbOg61ywRXkrU5JiwNAtsNvXkgzLv65g8RGo6FdkO2W8f3h5DiLXyz/vFhaLCqrkIzdJUIaG1CQAB0KQMIRDHWSOoDEqj68yY30Yyalh7JZ4bUGttDy9DCNlpMCqzfXmSELZgHjw+2Xc99yXoD/oBSwVOOFkNCTPFA5QYqUwi5l/3zB59ckwi8v6CQmbLrxa0r4gumwmd9URkzOHf+UtxuWlSxAARLyvdaGNwwV1yBIK5ll5SbArCTBxIECX5hfLhoVAQMu6rPu2opbyG/WVuMaI4zmwSh5oP5ANdDMiDxD3JuxPAOvG61Bn2A7Txgxvt3XCZGQXDKKcA26uxVuHXHSaEgWEXkQFiqS3POY4awGtlYZfnKxMAPFqKgnBEwMMj73GBOI2IcKCRBl/R/1V+8Cgk/uoKfVewMXeFUp0YvTEuNEAXQkKs6ObK2oPXLQYfV75e8Bbp+N0kAZTyxo28y6n8h8RIedwV7Qga6pMtz92nZtMTLmRpnbNQQpIgIvMvz9FepE6lfmkgwCjBfDqJBdu7UNBKoh8ObYaXJM91JxCbWFDHWPOUwSbgotO8K1MLHj6MPxiVd4v0ZIBJkUZEJAfhKCdJYJqiDBUTtUN4nw7Lx9xI0gxQo+EZrBH0HvU7kOVRnXYJ7KbdR5Q4klso5lNfwAUiLOFNFJ4YHcB0WGSohMLzfuhnA/8ssTOYCoKEzKV+roRA8MEFJojQepk/tKdqi44/tLohYjLm7juwbw37d7Q0pEMWPkwvETEGuUX2kkBAnnN+ba3cECZ9Ay8pj4oMf31ZIsFfbdhBuCIcY5ZbxAaLA4IcSwNdiMvMThRRIVLRmBGv8wmuEjSdKm8FXeCfUOq8422iiyhywlgWa19kkw9wXeQV4t1g/dJIE+6nPCIGkTwm0fqccK1UPWBc5vYCjCdzKqLgqB9dyRGpo1SxBGU+eImgg5OjIbC1LwyqwALvkg/CFPNDYBh9JQnNy0FTx98D5NQT/pYmdR4l7NCPUc6xnPdYI8ReCYyZzgV50JfAbWGUt3+KKxEgOKki/ByUTFjtRhPWBI6pkwhoLNSigiyZGg0ZyeMsS5jVzM8ycKxpf5/xoEnw8K3kJAHZwv6n+J/6X9lhPW/DRMTZSs/Mb6dkn/+c7yBRqIIBJ1tjx6Haj0ooLhhVlLzgyWDVQUsosXUG1wzmDyT0YHWfYo0hJQ95kcRIYVyLc4STJWeDHqBs6BfHBdRX4fZCR2g7wJ/qSG2TtPGV09e3lhtcabVd7pJAM8BAhDbKieoiqdfACvp1rYteKp67zPqZArOv7RPhKEXiWkMP1qJmbdgUwQkmyeSYZUpQEg0AsIlE/28CQSfyLX96yaB4I93Cb9RFyADU2hkhRHXy7UQsOsu01V43m+X5xLtRgK9GiA48P9YdDhC4V7tBBl7120SYe4WBt3CGWnOcLyYbLI8ltI4wF23HBQJCViExDsF5XTS30zYiIxSGq4oSnIEYZNgWJy1p6gJXM9QBCUsP0B7Fg0xO+EIHGgA7mORad6Nxd7moCOyhQFt5IWmWehULWnhO4Hz7CRWJR8gwsN8qJg4BFyLrzpmDupwJdDTjocV+HJyBfQmWV4vT7m/TYEMf08FthUMxP6OE9X3nQfhhLiuI0QGqgzMAcF6PC2kMFABjVQYeGwkYkOpYnE2yx7wRKs27Z9SDL0fdSSG/DA1NT3SE49aQALgkLT4N/QXEhQdEKT11lpxA7lU+ods42ZeTpsT+VoNxN3SOZA7Nq4hpqRUFR3sxAngFXxN4ZdwI4FJSKiHXLXPghLA7CR9SzNXviapq4oucXCU3AlbrAEgM3EVKRErl77SYlctzkx7QC8k39b7Uk5T9h547zRhxQRadv+cBHlikuhVOBxomVwwPBhb/Vm6lZDFqG1kKNQCUoSkYcoY0FYcyqXdG6+sBboFvC3YmxIClJLrXXtQlUPF+JamU5/RoegpkwGaRt3vsAEKe7hXRpZzWCFXZwO9QY8wFY2q5vVySByB8eAjH8iRlCWoGQwG5DIG2z1hiwh7EfSi2JO2GrmB3qqerqaRvCuWlTw7ILzR3lc5M0yY3KNSl0lixQAJE6JqoU4Qgdm8BpivMmyUS3TshQ6BIDfkkZ/+qBsZomGXpe3G5G8i9DiL0YOqwyPTvDz7bYhcYMaqCSIve3NhqzBqX695vsr9HtkE0MiclLGeFB0PFz5OvGCzSNP0/0JC5EIVlReAybBo/wZGaqXtevabtdS4z0plerFF1QkiCP3oFLvOGvoISMicGjISpwZCpMkyOqcbjJAwk06yO1LknzrMN4UkLyOWJyh9mroQ0Lk58NyEQacgWJjSz1Bh1VKij5YA56Soxv+QVFQEsrGVCSEW5LigSlBOdjuIVFxQ4oOqUJhhsKwEQP4NuQLMjgjw9Fe3cH6huKBekp6OxtwQ74xJRueiA+danICqZpr3SxAQYrBDHPg+qqg9gJtdtoSUtsJJ19bQp1C0MydORx0NmHCUoGcqIvk/dGvw6KQMEIdTaiGsR/i+J3VWAMGp/xawd6wEAgylKR7YC0lIe+BIRFzNeYoQbLUiwNGRb74CSUTHFSHjIOp29srlMyKBdkC8Ej6s10kQJ4mubsAGvWBfyOaB+djVgOOFNTXrhcTmAHLeFLinRBcK1J0Td3jmiIeB7ULDqSAk15ePZeZb4UyM5JTmw7T78Bi+Xr3h9V+W3evQF1qlD+I6zJPxSYgE3jSoEoOP0yXwky9aPtYjLC3clqvB4lQQXKiDLxHDH4YD9r0Zq+ehCSG3oNATEKJJCSbUbMw33FZTWzBO9mi1iJVngZ5BqM7pv1seZa0DfPQDKlGEQOu6nwMwyevlRCt8APCnpUz43nOluMpCGADgtWnJAefjgUybWJXvTahRKjN1nyV6gUwMkI6MEsyGeJRP5c6wjtlFRWmUQ10bXk/CSbFKIGoiFLKOlIIawz9UmcgpmwFNQ96LWJrScJHZ7n8Kj++BYN2flhWQJToo2u0TYFF6Yy4veUkkl340XxuPwsVgUUWfq15rWyXK1RDvDxISfALYUlQwVeAvmobEou41bVr6egECHU24/baKSPbePiErWElhJiEBJefsLt+UyjlvrtlgXpTd+NENDXCGt+A5saHEm+UMvhKbKo6hcCStRzRU7wHqGVW1qTJ0JKZWgXHIGwQCjwrOUKEsJwOLcjYXkeHMoa9KWrAS5sLWJL1APfUxMFSb3Abx7yQ6gijhoXHnZIpFyNlbWbJbWpPElWorUsfxynoF/TABPyJkkNCVTVfhHD1nqtAfmwpUeKIUGDZJ7dXF6TcXoj/Ax08f+SDv0cHzx/54O7L73fnLEAF5Sfsk1tkSESXQezrdgan9vTI6ScRIqaL5kRaqrVwBql8nTdKEjWJe9BOhDbFMNllaeuFgcK/qwjiJwaE5aHWyAQEb0rq7Zm/p8tE/bCqKiCjzrLMPcCA00QA9enjVtu3kbkM/vZlKn4NlYnqn0JbdKm6uE3NtmW1BS935dEhDeBElSKyHOBAfjM0tKQaijkx4f0wMx/AoRrxQdi4IE3WAoJRfuWFEiECUKgFsGTXB6pRi3mZdk+3JRzkHryLiWMdYCdSO4MBXjUtehXeU1ToB/wwFcnIJnhdVdleWflutQIfT+v8wXN0dT/VvMCrVGwWPsjg7QwmDgl47eBaJH6M7pTb2g4Jg4jy2hr/A1meRJF1SIn8LYNHcd+DlEbcvwdy0qbSkDsBh90QZKNiqJDZTtux2uSCB1W0RvEg8DplCR5XNO7dD7PO9wBGohDB9x6FoWplWIvBwat4kH1rr+I4n6XU8bljByAbfDT6D/gtU/QUO2uScQ8yEeTMCtpWy45CpcIinjpKaA6E9cMIPaB1tLvYZRzBQAgyQQ/olapDrXFu1645Uepd0Vu0xZV0tGqp2WKtSbGRB+sS9RsBZLrkz4J+wqsWttymLFGM4z2hQUjgXlL2nDApTnw/nl28AUFhZxit9sbdxp7bLjo/8VIVkkHkqv6ckzW48ltFDjbDwW7hRVTtLD6eQNuSAA0wtOOLBUvte56UWH0q88pfkg4XYqFq4ddr83nJQ3YEPChSCpT1IitoCYVL2jCR0Sy8Xa5G+mbGi76CYagZUS2ynZf26DJrRGARKFLI9Rt2jbshhy4hFVFxsKGBuvgPsu4eCdCeg2g2RnWlnqrtLW17w81JInNRwgtgROlUCbc4TbuLR7u7Tfs+iIG+yqC0AYzEK/H7HqjN2trBG3x2n8CYNvvXjhQk8qVsdUxmu/RucKDB1KrseWonouP7bz8uaCMIPbvRYjFpF97zHCoXW75Nu828COALaKup40ZjJPyIsaLofBHvYxKdICEKATmgAyQgH0OXE6yBQjNUizRBNGfi24g6OIYPxUCohNCTLAjGb1X0+lZTt2hDSx3jegZiHcmlvkPVGRcwbOuABgIO5bFf4VAiF7pf0N7aCC2PmU5JhzMhZu0XuLcl1pMEsI7+IRVkRgm+DZbhCgkqU429687VQXq0Wa9dY4I9GXLefFkLxMFXNQeVUhoI/nxQDervTwGu9g60MxqEWzrFVUjIEoCbJTlMqgBzEBFP8h6ljA/kjeMyrwUPpyM/gDgvZTRYy+iw9qHUmvbDAIRY9fZacBqudPiOR6jlQkVnlmEPnfqFGwg+vyAjcYSbEVY1WoG8E+Jz0Ga1YNOpXcSt6IUgYmFIteDUKSqBLEZeorsT9crLE9YUnd5IEiQNuDdSfVTRlQUpG0tw+895SP1qXxzNtdRbwQwbfgf9ig2LtWIc3dZ+Y1cDcLUKOD+HytT+S1wiPbnpCU5DLTFISeggAlerxBpRvFLclX2CDh8h3i50xTXsAdaw7Z1Vq0yIugKHmiKJAw4YW8nBgCeEGgEt/DMxm7JeDrE+Qnbhns3wT/voH6iQelQLudZOWtvuMBeoYCEA5MR29N5MO4d76zAoFE0lXA6VUAZqdXB7TA98UHehJRBHQERmbjOcNzoJSdcEq9StiSr9UhkRFXERS7LgNZ1M6NIBVccAvfYbUIpV58TVaTV1ctSEmzr6BbfjjiBOMnE1Rq+mOyyOHn2qdgIND4Ck5DqlZkrciE4YSs4pjIP8sCPaSrgnvXxs98hiREbB8qguoJZ1xRkSc+QY9aP9AFV+PToEe2owsFptaPTsNOa+R5a/iawM+Re88o71r4+XFSaS0HfVaYkqwOQndA68jJtG2BYwB0ELdG4MwHukkBxiuKCcd5dtnyYE0pm8FV9lwdtN3TcxLMIEg6PeTtO5DK+TkVwx6nJo87NRVYfxM4OHwghqEE7Y97aZjrYrjsds8yMsMl9qMh3V0lGi/spWhjQxX0fli6SaDyqBXD5qlwYET32PlrH4qGTEdLtWE8kSf54tK6p3HWri4ePHw58fT1+QPyYW7cO6JjzXuhTFgC94ob2/vfszcB1+f5fkwctrK758ze6Xl6TIL7HqKBbs8F5Zx1e8Ab5qUa6s/gewhCAnswFSwpEjTuJEScuBYoMTkVratcLq5PJ9Zdoi3ShPYCrBoXcDMMNr2i3uqCC5EQw0BKr+o49WS9dO8SH2lNuSuYQfWVPolIzvgDe4ulzU0lD9WSaH+oBuUtCW4lRPdQy1XsfsXc4fTDl62aAY6wVOZFIAM3PTJlqGeZ5mlPR4tz4H0PX7ZqjoC0Gjfedscv8+R3EeIB4rOhu2ExSuJ6GiQfd5P1rA2iIdWe9haqcnFD/IKWrR/hRChqIaQWfUfDsFXRckthn58IiIKb1MJAxTP/rdaYppVtw6D+l+NnWIcPPUkXauER1KRKy90yFxz88AcXxYsyyxAv1r90baPJF0XdKk4AmQTmpTSOzdM6T5cJUclvqGfRhR2DKdQC1gIMOQbj3D0hCYwWbkQT1y4JlsnEuMtg8QAEDes1Xctuj8W1Jn+eTn3gG8bbgrecqzhuQnlugepAbQqo7eLt27QxexseaLAoVqCaGaC+Uw7YeAoTqnJXREgzoBeZ21BAodUQMltUQFxuUrihXwxOvDIfykTWjcW9prxPnE7Lz35AsahdTWqRIWHwoE3gHwgF/c5z2ydM/cR5lgVFH3fAvrZ/lARBOKDWQuiOsFgyakGzxlQE/BnptO92JdddeB02cwynuYBVRQD4J0bWZqtlO0SAPs9WEuccSaRfZY9rUsR1wVDqZJymnfUGdbUGfk6v1k1Lmb0gFspk7yk4HB7mpUL4AhO31GoC40AETEjLjFmEt7mvrkU9dGvA5ykWGQu47DVsSiAFRtH26SzoiIL89oV0REgbHq9uNaJ3CDFfBD50W3DW3vSN/xqFx1HjlLITM1ndOjCsCXxsLodFHQQaacitNJZN6GRAbWQ9ELlSNNjVOBgj4Cg+3sautTtPqQxlYPWgbNU8B675q8nzglfPnYCGTozulg691rlTikgKBZtLFTy8nGQ4FLIBSMRslqnohwpUYSMRwoPhiVxEB1jTzD8uoErX6tS9s7a+MDeRXrY1fQnMLAXgcPtM0d/+3PHjy/X9C6sOCJYhZdk0rNN9H+xBBqTtobYsogBIYQCT2QHWDuQzmY085Y0p4NsoJ5sUS+qGUXHMDasW8IzHhbbSQ42dlkBJW44Z6ZB73iM/RTDVLS9p4bA4b28MDFCg2JEJtAmDJFF4OQwgBSY+lwY28U8lKTjKjJWSBpCVhV76HpUw4HLYYQ0aZgUYNTOwlgFlW8MMJqPeqcjtCHGQZB7UjP0nG5WlrVLl/VSVIIxs+NG4raRqri+aVQAS64VZ1VQz2Ds7Mkfpc/lxDsX9cGCg6mRuNjWZgZM+6IMyzVewobh6g0lySuBjOysKgcxqjfP/cFOuws5Ne5BzWZAXZt6jTPDbeOUbBiWF+dZyZNMSjgaFE/lvp0QJ2Elj5UaGokJh11U7ULnJnUUC8T8jLt+ngWq6CsdRBOR2CHkgCCQmZp1wY//1RtWKBvBK7a2UNKR3IAFL9giFxCkAuFcERdn3bAOuiIFggKtCFVbWAUB+4oobERAICmhC/qX2c9B4ZbhHJ1CTggb5tZnaAW1+ygExVM1pED+pAS9P9kr1YUFqMh3QEkcBwDakJtamrj2moIqy5n2RzgV+39NAbq/m7UM0MPVfkHWR8RVOWyIZPRfkZSKM9iaNcjp6bN+n/yAYTnX/6kwgFdms6OriuLZ8w69xa9QD9m/Fp6kT437dCSi3lOdWbVoG36SGZQuxKzW6r6JmDte5DhatsEoitxfMsgJGYbC2XqDangOjmOLcdNazfinpZRQzEk7D8r+zZU+QJElM9lB9ZKQ97r94TdFAxoodUbxwvk96MUZjoyrw/F6L1V7Qxi094DR/vddH30CQt12fUxoj6lBw05PbRDhavxEtUCHHUVipwF3pcZy7GQakgXfFVQdj21CSixLg2y08d7JiyojwU0Sqfq82gjg3TvbyJce/RxwFeF+Z/d3Tdq7pcLf/fr//GN9DEEfMfz3zqJ2QWvhHQ9AAAAYHpUWHRSYXcgcHJvZmlsZSB0eXBlIGlwdGMAAHjaPUnLDYBQDLp3CkeghWhdx76LNw/uH+tLFALhY+d1ly0TCmMqtGtAzR8+vBDcOiaDoDPb+Wo+1c/R60q1g7AYXwXsATEgFNfoGvlqAAAPW2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6aXB0Y0V4dD0iaHR0cDovL2lwdGMub3JnL3N0ZC9JcHRjNHhtcEV4dC8yMDA4LTAyLTI5LyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOnBsdXM9Imh0dHA6Ly9ucy51c2VwbHVzLm9yZy9sZGYveG1wLzEuMC8iCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDo0YTZmYzJkMy0zNzBiLTRkMzYtOGIzNS02NmRmMjhiYzM2OTgiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NmIzYTcxYTUtNjQxYS00YTU5LWE3OGQtODQ2YzFhYjYxODM5IgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6ZWU0ZGEyZGItNzcwMC00MDYyLTk5ZjItZmNjNzNlODA4NzYwIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJXaW5kb3dzIgogICBHSU1QOlRpbWVTdGFtcD0iMTUyMDA5MTY2MjU3MzQxNCIKICAgR0lNUDpWZXJzaW9uPSIyLjkuOCIKICAgZGM6Rm9ybWF0PSJpbWFnZS9wbmciCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjkvMi4xMCI+CiAgIDxpcHRjRXh0OkxvY2F0aW9uQ3JlYXRlZD4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OkxvY2F0aW9uQ3JlYXRlZD4KICAgPGlwdGNFeHQ6TG9jYXRpb25TaG93bj4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OkxvY2F0aW9uU2hvd24+CiAgIDxpcHRjRXh0OkFydHdvcmtPck9iamVjdD4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OkFydHdvcmtPck9iamVjdD4KICAgPGlwdGNFeHQ6UmVnaXN0cnlJZD4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OlJlZ2lzdHJ5SWQ+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjljMjhhYmUwLTJiNTEtNDcwYy1hZjYxLTIzNWE5Yjk3ZjcxNyIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iR2ltcCAyLjkvMi4xMCAoV2luZG93cykiCiAgICAgIHN0RXZ0OndoZW49IjIwMTgtMDMtMDNUMTU6NDE6MDIiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogICA8cGx1czpJbWFnZVN1cHBsaWVyPgogICAgPHJkZjpTZXEvPgogICA8L3BsdXM6SW1hZ2VTdXBwbGllcj4KICAgPHBsdXM6SW1hZ2VDcmVhdG9yPgogICAgPHJkZjpTZXEvPgogICA8L3BsdXM6SW1hZ2VDcmVhdG9yPgogICA8cGx1czpDb3B5cmlnaHRPd25lcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkNvcHlyaWdodE93bmVyPgogICA8cGx1czpMaWNlbnNvcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkxpY2Vuc29yPgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+txeqYAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+IDAw8pAmNZfO0AAA7kSURBVHja1Zx5kB1FHcc/8459b+9N2GSPhBiBBAQBBeVSI4GEoFQFSIASj+CRshDFQstAgXiiJcT7SLQKUVkQhOIIRVFA1gSSYCFHAgIakFSwSNjdhGU3yR7v7b43M/7Rr/N6erpn3stuTJyqrp7p6Zk3/X2/4/v7dc84HLrNiTme6ObHHFe1pQ4hOM4kA+cfpL4H5V+sFiS9LepY7W+SFj/mGEvbYQuUqTYVUx8bOL7Wphci6sNa9UzAJLRaBSdhuZ9nAMjTah0s50BUL3WI1M4ETkI5Thr6mFRPB8Qt9fdKfVXQdJWrCrDUIbJRaEAkI2onRvUkQJ4Ckqv09wygOP8PqudoUpRUSkqrdUlzNJA8pbhAUQHJjfB0h43qOTF2SZWYVEQxSRYGSZIgJUt1Aihoz+FrNs2pBrTUQQZHB0qXIhWUtFJSSp3UpApNmiRIhVJJWOgHBlV0KpWy1CQC5MRQgYRmf1SQarZv5w3fB1k8L1hHtet9fB/OOovZMUTTq4aMpg6SemFx/zpIUoJqAk+qPKrjBAEwtct97foaC+cqWoioE6WSziRJkWPwZI7B9auGO60BlQEyr7/OUyCkxCQpNgnzfXBdOPNMPgSMKWW8VAqawXc1ahFJSlOTpHI2XmSSJNU21ZSAysiiSofjlCXE1xRCbVMlC8hGPG/B0KZyMSspTU2CytkkR69VA65LkyxZXfVU0DwvrHr6fgnwqDhSfWZPoRGRpDQ1AWmqlBMlLTRAV7saIKPbHN3+qLZJBVPpl63gWeV+0RISMVHVM0X4CU2tkpprT0eApEtVZuvWsuSo9kcCItvU87K/ApQpZowrRQUsXw+ZJipRJl6UtoCRjgBKgpX2LWzGJEW6mpa2bIRJsDF94tIyzgRUzkQcazQJSa9YcdJxS5emL25r6zk9kxlsg3ytyWNVwpeirrH1LxYzuXy+cffu3a3PrVvnPtjV9fqrmkcslOpihEf0nQnYpWSUB5s5s6F+zZpjV7z73S8s8Twv4bphdalk8FJqJud6x3vrrWMfXLFix8q+vpERBTAbWPuBSk5SQBswzDNnNjRs3Ni5qr39lQWe5zsmNVENsNyXA1MNuG6jJni909DQf/yCBe2nbNjgdg8PF1xLLkvPY5GcgF1KGIhjBshs2HDi9e3tryzQPZg6oAqYdQgMlSZUe73ank7v6zzjjKOnrFnT/zcDQEYSmphEY56SNmn27BeW6P+o6qksRjj8I05wXw5atsu2Sq5Xjx0HOjpeW7Js2ZzjIoLvwNWJCRrzEFCXXpq+2Pe9RCUG2wSeCoSNfJpUUmXxlV3vJ84+O3mxBahQOjpRJUi27OR+WtDe3nO6DkTcflSbLlU2aTFJqN6mX3/EEf2nWxKFIb6YOkCQsAW6mcxgm+uaPZVeEomy+sggWDXKpvguLsMQFero19fU7GuLyaQ61TBz3QokohJwjpOvlYPWMwAStNFRePll2L1bDGT6dDjpJMhmoVgMDlJl4rrRNsWDqgGPAg0gkRivNQBlmgWKBcoEUmQiznWFJNkI4OgodHfD+Hj54f/zH+jthYULy2Dpg/e8sBrFeb1K8llxkiQD48QBZgmSttxSoSAG6rog92VxXXjhBcjlymBKIMfG4KWXwsZf5UYmr6Z6Mt2u6bRCbbdoiGOb9UkdQPbSFNvJ/RoJgEmqAPr6xDn54OpAd+0KS4+uflFOQd7LRjz1eypA2TK0kUDFTQzY8kspICWlyASS55UlSwXKcYRhVwG2BcYmImmzXbr0Rdkry9xhRYk7HSSnghmU1NhYUG10sKZOhZ07wyDJc2NjkEzagfI8GBoS6lsolL1nNgu1tUFiWokDsKharNczzeYmLEFw6mRouhrmz4V5dXBkFlrrTytJzzRwW8GdAWNnQe408GthzhxhuAuFMki+D6kUHHNMGVRVJWWdy8HAQJhmFArCOezbBy0tkMmYUzMRADpx8whOFSleKTmZ38EpZ8E1jTDPKadeIze/BnKnw95lsPdIeO01MWjHgdZWOOEEIREgJEpKmaxHR0V/gPp6aGyEmhox2Hwe9u6FkREBdEuLkLC4NA7AokWcA4yUyiiQV1IwMpvgparJEqyCd82Hb9XDxX6VuSxnHOo2idJ4LtR9AYonlqXKcYT9ksBIlZLkdXBQ9GlpEUXaPMcRAGcyAqz+ftizB6ZNK0urPlFRqV2rNNYLgLUJzvs4bKiDJf4E11U1rIMZyyH7tABHpxHS4EvyOTwsjmtrobnZztKbm6GuThyPjJRV2ASKX+Xqg1TE1NN+kF6GrzbB930DsOljoe5CyC6E1CxIdop2tweKb0K+G0bXQOHfweuSo9B5I7y9HAYuKUtIIiHslVTBYlGoHQh1M6VsVK/W3Cz6j42J/qrNs3nTiaw92A/UP+Gr9fAD/Q/InAotKyF7juXGc0XJLoCWWyC/HvZcC2Obg5npabeKgQxcUla7YlGAJY28ZPE1NdFJO8cRtkmkgM2s3UQvqlW9kDQ9C4sa4Ca0MHrqb6D9OTtIpi17jrhm6m/Cofj026DumSBbl1xMjxf1gPlA81HVbgkLJXAehVnT4Q+quiWnQNtaaPyy2YkODcOu3REi7ohr29aKe6mSNeMWSPUFAZKSIY1yLmdPBsrB5/NiX6pvVD5roqrnAIk58D2gWZWkafdDZn6w8xOb4LYu2PpvYXRBPGRHGyz+OFzxSWhu0tR2vrjXrvPAL5Zt1vTbYeeKMtDFIqTTQuVyOeHVslkBnOrR1IHv3Vv6jYw5H2XxerGLzBImXvV3ODkNl6knpvwiDNK3fwhXXgPPbREg1dfB1ClioDveglW3wseWQm9f+Icz88U91a35SchuC7N61ZNJIPRwBAQtkFJXXx9WM4tEhSYSTPN6CZN9mgY3qBQgcyo0XhUc1PqNcM8DMPcYWP0z+MffYMsmePqv8K9n4dH74TOfgHcG4IbvmcW58Spxb3VrvTtIEGVcKGnBwIAInvP5MqC5nGgbHCz9qVOExzRNx0dxYiIWk4VU72dQn4SFalvLyrBNevpZUd/6K2hvC4v4UbPhxhXQtxs2bIJCEdKGgKllJew6VwFvc4kf1wepQDYr7js0JNx/LmfOR02ZElS7CvJR+koW4zr0EC86X4CUVXmSybvl88JONDZEG8GWJiiWclM2b5ieqzxQARq3hMMOECo4fTo0NAjbJW1VOg1NTeKcBLSKfJS6YNa3qGLYRmXgfLWh7kLzAN93khjIim/Bzp7w+fFxuO8heOgRmHs01NXaway7SFPJ58N9JGjJpCCSra3Q0QGdnSJcaWwUTsS0vsqW9dSAskmTedmPA0cF/vGF5sFddAE8/Cis2yDs1cwZ0DpViP3AIPT0wvCI8Fg33RjDsRbC3pUK2++1J+XUsCQit1RRPsp1a3Iw7mpSZXrjwQhUR8CIzTL/cDIJq34Kd/wFbr8LduwURX2QBWfDVcvhhPfEcBTtN9ID8aTRBJ7JZkXlo8bHm3ZBv2sBiUigEtCh3lfGbqatvg6u/Dx88bOCaPb0wkgOOtuhsyNa3Yj4jfSAOQcexbpNK/Pi8lHvvNP6TAmoorKCxTPRhZTBRca17N9cV6jdzh44+b1w6vuFkX/4UfjtbTCjEy65EGbNjEtW2dXFBpJNcipfX+V4Tz7pPlhayaIDFVK/kER50OvAnP1g9EKq0QzSsivh+S3ltu9eD9u2w533lNv+fC/86bdw4vERgPcGjwtTgyliNWdli9tMANkWzopJjmMf6Op69VULULFeDyDgw4pvmgf3wMMCpFlHwpVfgPnz4Ce/FsAsWSz41fIrBGO/+efRAqX/RvEIdZIymO005ZeipMjUf3h41vPXXbfjx5TfdnAxrIky2SjVeG0HPrqfL3WLVIm+vfiSqFf9RLBz34eFJTf//RsEt5n3IXh9Gzz1d8Gj0mnzgPLdmkR1CFAku1ZTwqo6ynjPtj5KneoS+47X13fcA9/4xps/Li0kG1ckqhglUSEbNQaPZeFzsmH0IZFPCqmoZMIt5Yf74Cnw5o4gIDM6hZoOj5T76tvomuDxyGllYNT8uSodOkimeT1xPpPL55t29fdPe2bt2sKDXV1bpbqNK3lxVaqM5DO0/vpeaDgN+lR23rYuzM7vvg+++yO4YBHc9M1yEKpub/fD0k/DyChs3miRpvXBEMaFwkfgSz0ikFGXCRYjBqK7Bf31NFkKWilqEqW+rRUAKqGL2GUw4sPjatuea8OeaeliOOYoeORxWH51GICtr8HiT8Cut+HyS+zebs+1IQO5pQcGlJmR4VJRj0cMZVjrO6rUOaXOE17k6sYZ85TJSY/ADxthscwgjG2GodWlhB3ltOw9f4Rf/g72DRnUaVS0L7scvvZlM05Dq4KpYQf8u+CO0mCLihTIQRQxLx80pUzUpYauQbqK2rGnBciBINn6xvgb0OXA5Wrirm1tOCcVte3uh+mt5nNjTwQTdwC90H2mSD2rqlCMsR/EgOUZiqvVUUGxH7kY43mY3Qqb1Sxncoo5y1ntNvYEvL0U3MGAbRr+DnzqTnjLAJQXpxrYv3vgGQDzDfsmgPxKVq04W+FjdbBGzZs7KZGZbLyKA/rWxdBqGLwmKEkOeN1w9XLYqIBT1NSlUmmKU0PbMRF1/JT6Nvh6CkIEIW66yuTdQtNVpW07rDwHfq/ZIrdKkGzZyqiPRxCXXolalBGa29sGX0vDjyZzAlRK0g64+cNlkFwDQG6MAY+KGv0Ka1tb5MoN4/vAW+H8WuGRmpmEzYd9L8NXFsN6AzCuxYbAxD5WE2fXDmjtQeBrFe+Bx3rgAz7c7UzgE0MO+Dm4/144d7Hga+rrrOMGAliM8FJxJe71jUpV2WqOI5cmvgjva4JvJuA87K+mhszUGKx7BX66FF6yeB6bJ6rq3z8Ym1PhOeMijtVQPw8W1cIi4OgEtCdKGVIX+jzoc+GNQeheDeu7BFMG87snHvFf6jkkIMUBFWXsK32rUr0m7tNFcQAdMpAqASoOLCzA2L5kYXPJPvHfdzpkIFUKVKXeUQcpCiio7KthhwVI1QBVCWDV3rMSiTnkAO0P3/6HIFcKwmEDzmQO9mCDdths/wXji1Blzxb9QAAAAABJRU5ErkJggg=="],
  117. // dummy
  118. ["data:image/png;base64,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LmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6cGx1cz0iaHR0cDovL25zLnVzZXBsdXMub3JnL2xkZi94bXAvMS4wLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOmViM2YzYTk1LWFmZDMtNDNiMS1hZTQ5LTNhN2IwMDk0Mjg0ZiIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1NjJmOTkxYi1kNjk2LTRhNjItYjFjNS1mMTFhNzAwMTIzOTciCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpmZmFiNmU3ZS03ZWNiLTQ2OGEtOGJlOC0yMjUwYjM0OTU1MjciCiAgIEdJTVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IldpbmRvd3MiCiAgIEdJTVA6VGltZVN0YW1wPSIxNTIwMDkxNjg5MDI1OTI3IgogICBHSU1QOlZlcnNpb249IjIuOS44IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuOS8yLjEwIj4KICAgPGlwdGNFeHQ6TG9jYXRpb25DcmVhdGVkPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6TG9jYXRpb25DcmVhdGVkPgogICA8aXB0Y0V4dDpMb2NhdGlvblNob3duPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6TG9jYXRpb25TaG93bj4KICAgPGlwdGNFeHQ6QXJ0d29ya09yT2JqZWN0PgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6QXJ0d29ya09yT2JqZWN0PgogICA8aXB0Y0V4dDpSZWdpc3RyeUlkPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6UmVnaXN0cnlJZD4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZjZhNTRjMGMtZWI0ZS00YmZkLWI5NjctYjQyMWFmMzdiOWVkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuOS8yLjEwIChXaW5kb3dzKSIKICAgICAgc3RFdnQ6d2hlbj0iMjAxOC0wMy0wM1QxNTo0MToyOSIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgIDxwbHVzOkltYWdlU3VwcGxpZXI+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpJbWFnZVN1cHBsaWVyPgogICA8cGx1czpJbWFnZUNyZWF0b3I+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpJbWFnZUNyZWF0b3I+CiAgIDxwbHVzOkNvcHlyaWdodE93bmVyPgogICAgPHJkZjpTZXEvPgogICA8L3BsdXM6Q29weXJpZ2h0T3duZXI+CiAgIDxwbHVzOkxpY2Vuc29yPgogICAgPHJkZjpTZXEvPgogICA8L3BsdXM6TGljZW5zb3I+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz73ndT8AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH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"],
  119. // rlc
  120. ["data:image/png;base64,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YzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOmlwdGNFeHQ9Imh0dHA6Ly9pcHRjLm9yZy9zdGQvSXB0YzR4bXBFeHQvMjAwOC0wMi0yOS8iCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpwbHVzPSJodHRwOi8vbnMudXNlcGx1cy5vcmcvbGRmL3htcC8xLjAvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBNTTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6ZTQxOTdmM2ItN2ViZi00OGIxLThkNTgtZWFjNTcyMjE0MGNmIgogICB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjg5NTkxMDU2LWNlM2ItNGIyMy04YzIxLTE0YTBhYTMyODU4NCIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOmE5YTcxZjk4LWQ2MTYtNGM3My05MmU0LTNmZDdlZDAzODlkZSIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iV2luZG93cyIKICAgR0lNUDpUaW1lU3RhbXA9IjE1MjAwOTE2MjcxMjgzODciCiAgIEdJTVA6VmVyc2lvbj0iMi45LjgiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAgMi45LzIuMTAiPgogICA8aXB0Y0V4dDpMb2NhdGlvbkNyZWF0ZWQ+CiAgICA8cmRmOkJhZy8+CiAgIDwvaXB0Y0V4dDpMb2NhdGlvbkNyZWF0ZWQ+CiAgIDxpcHRjRXh0OkxvY2F0aW9uU2hvd24+CiAgICA8cmRmOkJhZy8+CiAgIDwvaXB0Y0V4dDpMb2NhdGlvblNob3duPgogICA8aXB0Y0V4dDpBcnR3b3JrT3JPYmplY3Q+CiAgICA8cmRmOkJhZy8+CiAgIDwvaXB0Y0V4dDpBcnR3b3JrT3JPYmplY3Q+CiAgIDxpcHRjRXh0OlJlZ2lzdHJ5SWQ+CiAgICA8cmRmOkJhZy8+CiAgIDwvaXB0Y0V4dDpSZWdpc3RyeUlkPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDphMzA2MWNkYS0xZTNkLTRjYjgtOGNiMy01NzliMzFjMTA4MDYiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkdpbXAgMi45LzIuMTAgKFdpbmRvd3MpIgogICAgICBzdEV2dDp3aGVuPSIyMDE4LTAzLTAzVDE1OjQwOjI3Ii8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICAgPHBsdXM6SW1hZ2VTdXBwbGllcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkltYWdlU3VwcGxpZXI+CiAgIDxwbHVzOkltYWdlQ3JlYXRvcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkltYWdlQ3JlYXRvcj4KICAgPHBsdXM6Q29weXJpZ2h0T3duZXI+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpDb3B5cmlnaHRPd25lcj4KICAgPHBsdXM6TGljZW5zb3I+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpMaWNlbnNvcj4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PvOQ5mMAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfiAwMPKBseKeVsAAAJfElEQVR42u1bXWgcxx3/ze7e7VnWl2vJ0jnCrkiKbRUbxw92nFIwwQ0plIIcDCmUvMQUTB+DyAfkrU8u5K0USvISQp5KbUoJIa2NnbYPwSGxQxI3NdhWPmxJOTknyWeddLe7eZjb3Oz//vNxurtEDxkYdnfuP7czv/l/z6xAd4vQ3Ls820rSxnPSycB7AQJtMz2r9NwkE8tzR2D0AgjuylWORjf5hLTRCsPVqQQ9Eg1u4h65qpP3NP8XMwDE5ErBEBsRjaBHYsFN3lOefYaGEw064ahBHzdoVVCoSLQFSNAjHQEyUd9wFRbRSAGIFRAihT5mJi02g2gIwgW+UgNypZwiCAixUiMAdQWEqJtWI+iSKFAQ1BUPDJXjDDCckILgN64egBoZR0J0imgHlKDDyVMgKBeok84pNVCuPuEKEG5IQag1qqcxz2BERbhySdCmJTCZSo/IvwpC/sYN3EwSIK1xnL2a2ilNkgCPPoofW5yu2NExswJhYn9ozCMFIeWAfGYkylCEyE6Qa0/vSf+8xueoaxwtYRIZsUHnyGOUIlWMOQJECCC8fh3/AeQqcyut45AkAaIIOHoUPwOwptT1Rq0RhRoR02t0ugJHkdD5BRwnqLoh3wAiTKu6ukI0VzghDKu2qZwBoGAYb41pU30RrdMVtOkceQZ/QFWQlBvSWqCioYISx62iQe8bgJriGHXMsWJmjU5X4OAP2HwCX2MmqVjkAYRU5qn8q7pBBUuhKziMNb2va1x22ESDixA9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"]
  121. ]
  122. };
  123.  
  124. let uroURDupes = [];
  125. let uroFID = -1;
  126. let uroShownFID = -1;
  127. let uroInhibitSave = true;
  128. let uroConfirmIntercepted = false;
  129. let uroMCLayer = null;
  130. let uroVenueLayer = null;
  131. let uro_uFP = [];
  132. let uroPendingURSessionIDs = [];
  133. let uroRequestedURSessionIDs = [];
  134. let uroPlacesGroupsCollapsed = [];
  135. let uroKnownProblemTypeIDs = [];
  136. let uroKnownProblemTypeNames = [];
  137. let uroPURsToHide = [];
  138. let uroNullOpenLayers = false;
  139. let uroNullURLayer = false;
  140. let uroNullProblemLayer = false;
  141. let uroNullMapViewport = false;
  142. let uroURDialogIsOpen = false;
  143. let uroHoveredURID = null;
  144. let uroSelectedURID = null;
  145. let uroURReclickAttempts = 0;
  146. let uroPendingCommentDataRefresh = false;
  147. let uroWaitingCommentDataRefresh = false;
  148. let uroExpectedCommentCount = null;
  149. let uroCachedLastCommentID = null;
  150. let uroMouseIsDown = false;
  151. let uroPopulatingRequestSessions = false;
  152. let uroHidePopupOnPanelOpen = false;
  153. let uroUserID = -1;
  154. let uroMTEMode = false;
  155. let uroDiv = null;
  156. let uroAlerts = null;
  157. let uroControls = null;
  158. let uroCtrlHides = null;
  159. let uroAMList = [];
  160. let uroManagedAreas = [];
  161. let uroIgnoreAreasUserID = null;
  162. let uroMousedOverMapComment = null;
  163. let uroMousedOverOtherObjectWithinMapComment = false;
  164. let uroRTCObjs = null;
  165. let uroUnstackedMasterID = null;
  166. let uroStackList = [];
  167. let uroStackType = null;
  168. let uroMainTickHandlerID = null;
  169. let uroMainTickStage = 0;
  170. let uroSettingsApplied = false;
  171. let uroInhibitURFiltering = false;
  172.  
  173.  
  174. const uroUtils = // utility functions
  175. {
  176. GetExtent: function()
  177. {
  178. // From DaveAcincy
  179. let extent = new OpenLayers.Bounds(W.map.getExtent());
  180. extent = extent.transform('EPSG:4326', 'EPSG:3857');
  181. return extent;
  182. },
  183. ModifyHTML: function(htmlIn)
  184. {
  185. if(typeof trustedTypes === "undefined")
  186. {
  187. return htmlIn;
  188. }
  189. else
  190. {
  191. const escapeHTMLPolicy = trustedTypes.createPolicy("forceInner", {createHTML: (to_escape) => to_escape});
  192. return escapeHTMLPolicy.createHTML(htmlIn);
  193. }
  194. },
  195. CloneObject: function(objIn)
  196. {
  197. return JSON.parse(JSON.stringify(objIn));
  198. },
  199. GetCBChecked: function(cbID)
  200. {
  201. try
  202. {
  203. return(document.getElementById(cbID).checked);
  204. }
  205. catch(err)
  206. {
  207. uroDBG.AddLog('GetCBChecked() - '+cbID+' not found!');
  208. return null;
  209. }
  210. },
  211. SetCBChecked: function(cbID, state)
  212. {
  213. try
  214. {
  215. document.getElementById(cbID).checked = state;
  216. }
  217. catch(err)
  218. {
  219. uroDBG.AddLog('SetCBChecked() - '+cbID+' not found!');
  220. }
  221. },
  222. GetElmValue: function(elmID)
  223. {
  224. try
  225. {
  226. return(document.getElementById(elmID).value);
  227. }
  228. catch(err)
  229. {
  230. uroDBG.AddLog('GetElmValue() - '+elmID+' not found!');
  231. return null;
  232. }
  233. },
  234. SetOnClick: function(elm,fn)
  235. {
  236. try
  237. {
  238. if(typeof elm == 'object')
  239. {
  240. elm.onclick = fn;
  241. }
  242. else
  243. {
  244. document.getElementById(elm).onclick = fn;
  245. }
  246. }
  247. catch(err)
  248. {
  249. uroDBG.AddLog('SetOnClick() - '+elm+' not found!');
  250. }
  251. },
  252. AddEventListener: function(elm, eventType, eventFn, eventBool)
  253. {
  254. try
  255. {
  256. document.getElementById(elm).addEventListener(eventType, eventFn, eventBool);
  257. }
  258. catch(err)
  259. {
  260. uroDBG.AddLog('AddEventListener() - '+elm+' not found!');
  261. }
  262. },
  263. DateToDays: function(dateToConvert)
  264. {
  265. let dateNow = new Date();
  266. let elapsedSinceEpoch = dateNow.getTime();
  267. let elapsedSinceEvent = elapsedSinceEpoch - dateToConvert;
  268. dateNow.setHours(0);
  269. dateNow.setMinutes(0);
  270. dateNow.setSeconds(0);
  271. dateNow.setMilliseconds(0);
  272. let elapsedSinceMidnight = elapsedSinceEpoch - dateNow.getTime();
  273. dateNow.setHours(24);
  274. let pendingUntilMidnight = elapsedSinceEpoch - dateNow.getTime();
  275. if((elapsedSinceEvent < elapsedSinceMidnight) && (elapsedSinceEvent > pendingUntilMidnight))
  276. {
  277. // event occurred today...
  278. return 0;
  279. }
  280. else if(elapsedSinceEvent < 0)
  281. {
  282. // event occurrs at some point in the future after midnight today, so return a minimum value of -1...
  283. return -1 - Math.floor((pendingUntilMidnight - elapsedSinceEvent) / 86400000);
  284. }
  285. else
  286. {
  287. // event occurred at some point prior to midnight this morning, so return a minimum value of 1...
  288. return 1 + Math.floor((elapsedSinceEvent - elapsedSinceMidnight) / 86400000);
  289. }
  290. },
  291. GetURAge: function(urObj,ageType,getRaw)
  292. {
  293. if(ageType === 0)
  294. {
  295. if((urObj.attributes.driveDate === null)||(urObj.attributes.driveDate === 0)) return -1;
  296. if(getRaw) return urObj.attributes.driveDate;
  297. else return uroUtils.DateToDays(urObj.attributes.driveDate);
  298. }
  299. else if(ageType === 1)
  300. {
  301. if((urObj.attributes.resolvedOn === null)||(urObj.attributes.resolvedOn === 0)) return -1;
  302. if(getRaw) return urObj.attributes.resolvedOn;
  303. else return uroUtils.DateToDays(urObj.attributes.resolvedOn);
  304. }
  305. else
  306. {
  307. return -1;
  308. }
  309. },
  310. GetMCAge: function(mcAttrs, ageType, getRaw)
  311. {
  312. if(ageType === 0)
  313. {
  314. if((mcAttrs.createdOn === null)||(mcAttrs.createdOn === 0)) return -1;
  315. if(getRaw) return mcAttrs.createdOn;
  316. else return uroUtils.DateToDays(mcAttrs.createdOn);
  317. }
  318. else if(ageType === 1)
  319. {
  320. if((mcAttrs.updatedOn === null)||(mcAttrs.updatedOn === 0)) return -1;
  321. if(getRaw) return mcAttrs.updatedOn;
  322. else return uroUtils.DateToDays(mcAttrs.updatedOn);
  323. }
  324. else if(ageType === 2)
  325. {
  326. if((mcAttrs.endDate === null)||(mcAttrs.endDate === 0)) return -1;
  327. let tDate = new Date(mcAttrs.endDate);
  328. if(getRaw) return tDate;
  329. else return uroUtils.DateToDays(tDate);
  330. }
  331. else
  332. {
  333. return -1;
  334. }
  335. },
  336. GetPURAge: function(purObj)
  337. {
  338. if(purObj.attributes.venueUpdateRequests[0].attributes.dateAdded !== null)
  339. {
  340. return uroUtils.DateToDays(purObj.attributes.venueUpdateRequests[0].attributes.dateAdded);
  341. }
  342. else
  343. {
  344. return -1;
  345. }
  346. },
  347. GetCameraAge: function(camObj, mode)
  348. {
  349. if(mode === 0)
  350. {
  351. if(camObj.attributes.updatedOn === null) return -1;
  352. return uroUtils.DateToDays(camObj.attributes.updatedOn);
  353. }
  354. if(mode === 1)
  355. {
  356. if(camObj.attributes.createdOn === null) return -1;
  357. return uroUtils.DateToDays(camObj.attributes.createdOn);
  358. }
  359. },
  360. GetCommentAge: function(commentObj)
  361. {
  362. if(commentObj.createdOn === null) return -1;
  363. return uroUtils.DateToDays(commentObj.createdOn);
  364. },
  365. ParseDaysAgo: function(days)
  366. {
  367. if(days === 0) return 'today';
  368. else if(days === 1) return '1 day ago';
  369. else return days+' days ago';
  370. },
  371. ParseDaysToGo: function(days)
  372. {
  373. days = 0 - days;
  374. if(days === 0) return 'today';
  375. else if(days === 1) return 'in 1 day';
  376. else return 'in '+days+' days';
  377. },
  378. GetLocalisedSpeedString: function(thisSpeed)
  379. {
  380. if(thisSpeed !== null)
  381. {
  382. let conversionFactor = 1; // default to metric
  383. let multipleFactor = 10; // default to limits being set in multiples of 10
  384. let country = null;
  385. if((W.model.getTopCountry()) && (W.model.getTopCountry().attributes.name !== undefined))
  386. {
  387. country = W.model.getTopCountry().attributes.name;
  388. }
  389. if(country !== null)
  390. {
  391. // country-specific deviations from the above...
  392. if
  393. (
  394. (country == "United Kingdom") ||
  395. (country == "Jersey") ||
  396. (country == "Guernsey") ||
  397. (country == "United States")
  398. )
  399. {
  400. // countries using MPH
  401. conversionFactor = 1.609;
  402. }
  403. if
  404. (
  405. (country == "United States") ||
  406. (country == "Guernsey")
  407. )
  408. {
  409. // countries with speed limits set in multiples of 5
  410. multipleFactor = 5;
  411. }
  412. }
  413. let speed = Math.round(thisSpeed / conversionFactor);
  414. let retval = speed;
  415. if(conversionFactor == 1) retval += "KM/H";
  416. else retval += "MPH";
  417. return retval;
  418. }
  419. else return "not set";
  420. },
  421. Clickify: function(desc, suffix)
  422. {
  423. // The terminators array consists of pairs of characters which may be found at either
  424. // end of a URL. The first entry in each pair indicates which character needs to be
  425. // found immediately prior to the URL ('' indicates any character) in order for the
  426. // second entry to be considered as a potential end of URL marker
  427. let terminators = [
  428. ['', ' '],
  429. ['', ','],
  430. ['(', ')'],
  431. ['[', ']'],
  432. ['', '\r'],
  433. ['', '\n']
  434. ];
  435. if(desc === null) return '';
  436. if(desc === undefined) return '';
  437. if(desc === '') return '';
  438. if(desc.indexOf("https: one.network") == 0)
  439. {
  440. desc = desc.replaceAll(' ','/');
  441. }
  442. desc = desc.replace(/<\/?[^>]+(>|$)/g, "");
  443. if(desc !== "null")
  444. {
  445. // At the moment we can only clickify links that start with http or https...
  446. if(desc.indexOf('http') != -1)
  447. {
  448. let links = desc.split("http");
  449. desc = '';
  450. let i, j, linkEndPos, descPostLink;
  451. for(i=0; i<links.length; i++)
  452. {
  453. if(links[i].indexOf('://') != -1)
  454. {
  455. let prefix = links[i - 1][links[i - 1].length - 1];
  456. links[i] = "http" + links[i];
  457. linkEndPos = links[i].length + 1;
  458. // work out where the end of the URL is, based on what the character immediately
  459. // preceding the "http" is
  460. for(j=0; j<terminators.length; j++)
  461. {
  462. if(links[i].indexOf(terminators[j][1]) !== -1)
  463. {
  464. if((prefix === terminators[j][0]) || (terminators[j][0] === ''))
  465. {
  466. linkEndPos = Math.min(linkEndPos, links[i].indexOf(terminators[j][1]));
  467. }
  468. }
  469. }
  470. descPostLink = '';
  471. if(linkEndPos < links[i].length)
  472. {
  473. descPostLink = links[i].slice(linkEndPos);
  474. links[i] = links[i].slice(0,linkEndPos);
  475. }
  476. desc += '<a target="_wazeUR" href="'+links[i]+'">'+links[i]+'</a>' + descPostLink;
  477. }
  478. else
  479. {
  480. desc += links[i];
  481. }
  482. }
  483. }
  484. desc = desc.replace(/\n/g,"<br>");
  485. return desc + suffix;
  486. }
  487. else
  488. {
  489. return '';
  490. }
  491. },
  492. GetUserNameFromID: function(userID)
  493. {
  494. let userName;
  495. if(W.model.users.objects[userID] != null)
  496. {
  497. userName = W.model.users.objects[userID].attributes.userName;
  498. if(userName === undefined)
  499. {
  500. userName = userID;
  501. }
  502. }
  503. else
  504. {
  505. userName = userID;
  506. }
  507. return userName;
  508. },
  509. GetUserNameAndRank: function(userID)
  510. {
  511. let userName;
  512. let userLevel;
  513. if(W.model.users.objects[userID] != null)
  514. {
  515. userName = W.model.users.objects[userID].attributes.userName;
  516. if(userName === undefined)
  517. {
  518. userName = userID;
  519. }
  520. else
  521. {
  522. userName = '<a href="' + (W.Config.user_profile.url + userName) + '" target="_blank">' + userName + '</a>';
  523. }
  524. userLevel = W.model.users.objects[userID].attributes.rank + 1;
  525. }
  526. else
  527. {
  528. userName = userID;
  529. userLevel = '?';
  530. }
  531. return userName + ' (' + userLevel + ')';
  532. },
  533. GetDateTimeString: function(ts)
  534. {
  535. let tDateObj = new Date(ts);
  536. let dateLocale;
  537. let timeLocale;
  538. if(uroUtils.GetCBChecked('_cbDateFmtDDMMYY')) dateLocale = 'en-gb';
  539. if(uroUtils.GetCBChecked('_cbDateFmtMMDDYY')) dateLocale = 'en-us';
  540. if(uroUtils.GetCBChecked('_cbDateFmtYYMMDD')) dateLocale = 'ja';
  541. if(uroUtils.GetCBChecked('_cbTimeFmt24H')) timeLocale = 'en-gb';
  542. if(uroUtils.GetCBChecked('_cbTimeFmt12H')) timeLocale = 'en-us';
  543. return tDateObj.toLocaleDateString(dateLocale) + ' ' + tDateObj.toLocaleTimeString(timeLocale);
  544. },
  545. ParsePxString: function(pxString)
  546. {
  547. return parseInt(pxString.split("px")[0]);
  548. },
  549. TypeCast: function(varin)
  550. {
  551. if(varin == "null") return null;
  552. if(typeof varin == "string") return parseInt(varin);
  553. return varin;
  554. },
  555. Truncate: function(val)
  556. {
  557. if(val === null) return val;
  558. if(val < 0) return Math.ceil(val);
  559. return Math.floor(val);
  560. },
  561. KeywordPresent: function(desc, keyword, caseInsensitive)
  562. {
  563. let re;
  564. if(caseInsensitive) re = RegExp(keyword,'i');
  565. else re = RegExp(keyword);
  566. if(desc.search(re) != -1) return true;
  567. else return false;
  568. },
  569. AddStyle: function(ID, css)
  570. {
  571. let head, style;
  572. head = document.getElementsByTagName('head')[0];
  573. if (!head)
  574. {
  575. return;
  576. }
  577. uroUtils.RemoveStyle(ID); // in case it is already there
  578. style = document.createElement('style');
  579. style.type = 'text/css';
  580. style.innerHTML = uroUtils.ModifyHTML(css);
  581. style.id = ID;
  582. head.appendChild(style);
  583. },
  584. RemoveStyle: function(ID)
  585. {
  586. let style = document.getElementById(ID);
  587. if (style)
  588. {
  589. style.parentNode.removeChild(style);
  590. }
  591. },
  592. GetTS: function(day, month, year, hours, mins)
  593. {
  594. let retval = new Date(0);
  595. retval.setDate(day);
  596. retval.setMonth(month - 1);
  597. retval.setYear(year);
  598. retval.setHours(hours);
  599. retval.setMinutes(mins);
  600. return retval.getTime();
  601. },
  602. ToHex: function(decValue, digits)
  603. {
  604. let modifier = 1;
  605. for(let i=0; i < digits; i++)
  606. {
  607. modifier *= 16;
  608. }
  609. // make sure decValue actually is an integer
  610. decValue = parseInt(decValue);
  611. // adding the modifier ensures we have enough digits, including
  612. // any leading zeros which may be required, for the required output
  613. decValue += modifier;
  614. let retval = decValue.toString(16);
  615. // after converting to hex with the modifier included, we'll have
  616. // as many digits as we need to represent decValue, but also an
  617. // extra leading 1, which now needs to be removed before returning
  618. // the result...
  619. retval = retval.substr(-digits);
  620. retval = retval.toUpperCase();
  621. return retval;
  622. },
  623. GetActiveSidebarTab: function()
  624. {
  625. let retval = null;
  626.  
  627. let tabIcons = document.querySelectorAll('wz-navigation-item');
  628. for(let i = 0; i < tabIcons.length; ++i)
  629. {
  630. if(tabIcons[i].selected === true)
  631. {
  632. retval = tabIcons[i].attributes['data-for'].value;
  633. break;
  634. }
  635. }
  636. return retval;
  637. },
  638. IsClosureUIActive: function()
  639. {
  640. let retval = false;
  641.  
  642. if(W.selectionManager.getSelectedWMEFeatures()[0]?.featureType === 'segment')
  643. {
  644. let ast = uroUtils.GetActiveSidebarTab();
  645. if(ast === 'feature_editor')
  646. {
  647. retval = document.querySelector('wz-tab.closures-tab').attributes['is-active'].value !== 'false';
  648. }
  649. }
  650.  
  651. return retval;
  652. },
  653. ConvertWGS84ToMercator: function(point)
  654. {
  655. return point.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
  656. },
  657. ConvertMercatorToWGS84: function(point)
  658. {
  659. return point.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
  660. },
  661. GetSelectedSegmentIDs: function()
  662. {
  663. let selDMOs = W.selectionManager.getSelectedDataModelObjects();
  664. let retval = [];
  665.  
  666. if(selDMOs.length > 0)
  667. {
  668. for(let i = 0; i < selDMOs.length; ++i)
  669. {
  670. if(selDMOs[i].type === "segment")
  671. {
  672. retval.push(selDMOs[i].attributes.id);
  673. }
  674. }
  675. }
  676.  
  677. return retval;
  678. }
  679. };
  680. const uroTabs = // script tab handling
  681. {
  682. MAX_PER_ROW: 6,
  683. IDS:
  684. {
  685. URS: 0,
  686. MPS: 1,
  687. MCS: 2,
  688. RTCS: 3,
  689. RAS: 4,
  690. PLACES: 5,
  691. CAMS: 6,
  692. OWL: 7,
  693. MISC: 8
  694. },
  695. FIELDS:
  696. {
  697. TABHEADER: 0,
  698. TABBODY: 1,
  699. LINKID: 2,
  700. TABTITLE: 3,
  701. SHOWFN: 4,
  702. CLICKFN: 5,
  703. STORAGE: 6,
  704. POPULATEFN: 7
  705. },
  706. CtrlTabs: [],
  707. selectedOWLGroup: null,
  708.  
  709. SetStyleDisplay: function(elm,style)
  710. {
  711. try
  712. {
  713. if(typeof elm == 'object')
  714. {
  715. elm.style.display = style;
  716. }
  717. else
  718. {
  719. document.getElementById(elm).style.display = style;
  720. }
  721. }
  722. catch(err)
  723. {
  724. uroDBG.AddLog('SetStyleDisplay() - '+elm+' not found!');
  725. }
  726. },
  727. PopulateMP: function()
  728. {
  729. let tHTML = '';
  730. tHTML += '<input type="checkbox" id="_cbMPFilterOutsideArea">Hide MPs outside my editable area</input><br><br>';
  731. tHTML += '<b>Filter MPs by type:</b><br>';
  732. let i;
  733. for(i=0; i<uroKnownProblemTypeNames.length; i++)
  734. {
  735. tHTML += '<input type="checkbox" id="_cbMPFilter_T'+uroKnownProblemTypeIDs[i]+'">'+uroKnownProblemTypeNames[i]+'</input><br>';
  736. }
  737. tHTML += '<br><input type="checkbox" id="_cbMPFilterUnknownProblem">Unknown problem type</input><br><br>';
  738.  
  739. tHTML += '&nbsp;&nbsp;<i>Specially tagged types</i><br>';
  740. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterElgin">[Elgin]</input><br>';
  741. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterTrafficCast">[TrafficCast]</input><br>';
  742. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterTrafficMaster">[TM]</input><br>';
  743. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterCaltrans">[Caltrans]</input><br>';
  744. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterTFL">TfL</input><br>';
  745.  
  746. tHTML += '<input type="checkbox" id="_cbMPFilterReopenedProblem">Reopened Problems</input><br><br>';
  747.  
  748. tHTML += '<input type="checkbox" id="_cbInvertMPFilter">Invert operation of type filters?</input><br>';
  749.  
  750. tHTML += '<br><b>Hide closed/solved/unidentified Problems:</b><br>';
  751. tHTML += '<input type="checkbox" id="_cbMPFilterClosed">Closed</input><br>';
  752. tHTML += '<input type="checkbox" id="_cbMPFilterSolved">Solved</input><br>';
  753. tHTML += '<input type="checkbox" id="_cbMPFilterUnidentified">Not identified</input><br><br>';
  754.  
  755. tHTML += '<input type="checkbox" id="_cbMPClosedUserIDFilter" pairedWith="_cbMPNotClosedUserIDFilter">Closed</input> or ';
  756. tHTML += '<input type="checkbox" id="_cbMPNotClosedUserIDFilter" pairedWith="_cbMPClosedUserIDFilter">Not Closed</input> by user';
  757. tHTML += '<select id="_selectMPUserID" style="width:80%; height:22px;"></select><br>';
  758.  
  759. tHTML += '<br><b>Hide problems (not turn) by severity:</b><br>';
  760. tHTML += '<input type="checkbox" id="_cbMPFilterLowSeverity">Low</input>&nbsp;&nbsp;';
  761. tHTML += '<input type="checkbox" id="_cbMPFilterMediumSeverity">Medium</input>&nbsp;&nbsp;';
  762. tHTML += '<input type="checkbox" id="_cbMPFilterHighSeverity">High</input><br>';
  763.  
  764. tHTML += '<br><b>Show MPs based on start/end dates:</b><br>';
  765. tHTML += '<input type="checkbox" id="_cbMPFilterStartDate">Start</input>&nbsp;&nbsp;';
  766. tHTML += '<input type="number" min="1" max="31" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterStartDay"> / ';
  767. tHTML += '<input type="number" min="1" max="12" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterStartMonth"> / ';
  768. tHTML += '<input type="number" min="2010" max="2100" size="4" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterStartYear"><br>';
  769. tHTML += '<input type="checkbox" id="_cbMPFilterEndDate">End</input>&nbsp;&nbsp;';
  770. tHTML += '<input type="number" min="1" max="31" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterEndDay"> / ';
  771. tHTML += '<input type="number" min="1" max="12" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterEndMonth"> / ';
  772. tHTML += '<input type="number" min="2010" max="2100" size="4" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterEndYear"><br>';
  773. tHTML += '<input type="checkbox" id="_cbMPFilterEndDatePassed">End date in the past</input>';
  774.  
  775. return tHTML;
  776. },
  777. PopulatePlaces: function()
  778. {
  779. let tHTML = '';
  780. tHTML += '<b>Filter PURs by category/status:</b><br>';
  781. tHTML += '<input type="checkbox" id="_cbFilterUneditablePlaceUpdates">Ones I can\'t edit</input><br>';
  782. tHTML += '<input type="checkbox" id="_cbPURFilterInsideManagedAreas">Ones within AM areas</input>';
  783. tHTML += '&nbsp;(<input type="checkbox" id="_cbPURExcludeUserArea">except my area)</input><br>';
  784. tHTML += '<i>Requires Area Manager layer to be enabled</i><br>';
  785. tHTML += '<input type="checkbox" id="_cbFilterLockRankedPlaceUpdates">Ones with non-zero lockRanks</input><br>';
  786. tHTML += '<input type="checkbox" id="_cbFilterNewPlacePUR">Ones for new places</input><br>';
  787. tHTML += '<input type="checkbox" id="_cbFilterUpdatedDetailsPUR">Ones for updated place details</input><br>';
  788.  
  789. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFPhone">Phone number</input><br>';
  790. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFName">Name</input><br>';
  791. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFEntryExitPoints">Entry//exit points</input><br>';
  792. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFOpeningHours">Opening hours</input><br>';
  793. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFAliases">Aliases</input><br>';
  794. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFServices">Services</input><br>';
  795. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFGeometry">Geometry</input><br>';
  796. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFHouseNumber">House number</input><br>';
  797. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFCategories">Categories</input><br>';
  798. tHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbPURFilterCFDescription">Description</input><br>';
  799.  
  800. tHTML += '<input type="checkbox" id="_cbFilterNewPhotoPUR">Ones for new photos</input><br>';
  801. tHTML += '<input type="checkbox" id="_cbFilterFlaggedPUR">Ones flagged for attention</input><br>';
  802. tHTML += '<br><input type="checkbox" id="_cbInvertPURFilters">Invert PUR filters</input><br>';
  803.  
  804. tHTML += '<br><b>Filter PURs by age of submission:</b><br>';
  805. tHTML += '<input type="checkbox" id="_cbEnablePURMinAgeFilter">Hide PURs less than </input>';
  806. tHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPURFilterMinDays"> days old<br>';
  807. tHTML += '<input type="checkbox" id="_cbEnablePURMaxAgeFilter">Hide PURs more than </input>';
  808. tHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPURFilterMaxDays"> days old<br>';
  809.  
  810. tHTML += '<hr>';
  811.  
  812. tHTML += '<br><b>Filter Places by state:</b><br>';
  813. tHTML += 'Hide if last edited<br>';
  814. tHTML += '<input type="checkbox" id="_cbPlaceFilterEditedLessThan"> less than </input>';
  815. tHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterPlaceEditMinDays"> days ago<br>';
  816. tHTML += '<input type="checkbox" id="_cbPlaceFilterEditedMoreThan"> more than </input>';
  817. tHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterPlaceEditMaxDays"> days ago<br>';
  818.  
  819. tHTML += '<br>Hide if locked at level:<br>';
  820. tHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHidePlacesL0">1</input>';
  821. tHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHidePlacesL1">2</input>';
  822. tHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHidePlacesL2">3</input>';
  823. tHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHidePlacesL3">4</input>';
  824. tHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHidePlacesL4">5</input>';
  825. tHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHidePlacesL5">6</input>';
  826. tHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHidePlacesStaff">Staff</input>';
  827. tHTML += '<br>&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHidePlacesAdLocked">AdLocked</input><br>';
  828.  
  829. tHTML += '<br>Hide by geometry:<br>';
  830. tHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideAreaPlaces">Areas</input>';
  831. tHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHidePointPlaces">Points</input>';
  832.  
  833. tHTML += '<br><br><input type="checkbox" id="_cbHidePhotoPlaces" pairedWith="_cbHideNoPhotoPlaces">Hide or </input>';
  834. tHTML += '<input type="checkbox" id="_cbHideNoPhotoPlaces" pairedWith="_cbHidePhotoPlaces">show ones with photos</input><br>';
  835.  
  836. tHTML += '<input type="checkbox" id="_cbHideLinkedPlaces" pairedWith="_cbHideNoLinkedPlaces">Hide or </input>';
  837. tHTML += '<input type="checkbox" id="_cbHideNoLinkedPlaces" pairedWith="_cbHideLinkedPlaces">show ones with external links</input><br>';
  838.  
  839. tHTML += '<input type="checkbox" id="_cbHideDescribedPlaces" pairedWith="_cbHideNonDescribedPlaces">Hide or </input>';
  840. tHTML += '<input type="checkbox" id="_cbHideNonDescribedPlaces" pairedWith="_cbHideDescribedPlaces">show ones with descriptive text</input><br>';
  841.  
  842. tHTML += '<input type="checkbox" id="_cbHideKeywordPlaces" pairedWith="_cbHideNoKeywordPlaces">Hide or </input>';
  843. tHTML += '<input type="checkbox" id="_cbHideNoKeywordPlaces" pairedWith="_cbHideKeywordPlaces">show ones with a name including</input><br>';
  844. tHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textKeywordPlace"><br>';
  845.  
  846. tHTML += '<br><b>Show Places touched by a specific editor:</b><br>';
  847. tHTML += '<input type="checkbox" id="_cbShowOnlyPlacesCreatedBy">Created by</input>&nbsp;/&nbsp;';
  848. tHTML += '<input type="checkbox" id="_cbShowOnlyPlacesEditedBy">edited by</input><br>';
  849. tHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textPlacesEditor"><br>';
  850. tHTML += '<select id="_selectPlacesUserID" style="width:80%; height:22px;"></select><br>';
  851.  
  852. tHTML += '<br><b>Hide Places touched by a specific editor:</b><br>';
  853. tHTML += '<input type="checkbox" id="_cbHideOnlyPlacesCreatedBy">Created by</input>&nbsp;/&nbsp;';
  854. tHTML += '<input type="checkbox" id="_cbHideOnlyPlacesEditedBy">edited by</input><br>';
  855. tHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textHidePlacesEditor"><br>';
  856. tHTML += '<select id="_selectHidePlacesUserID" style="width:80%; height:22px;"></select><br>';
  857.  
  858. tHTML += '<br><br><b>Filter Places by category:</b><br>';
  859. tHTML += '<input type="checkbox" id="_cbLeavePURGeos" pairedWith="_cbHidePURsForFilteredPlaces">Keep place visible if linked PUR is hidden, or</input><br>';
  860. tHTML += '<input type="checkbox" id="_cbHidePURsForFilteredPlaces" pairedWith="_cbLeavePURGeos">Hide PURs linked to hidden places</input><br><br>';
  861.  
  862. let nCategories = W.Config.venues.categories.length;
  863. let i;
  864. if(uroPlacesGroupsCollapsed.length != nCategories)
  865. {
  866. for(i=0; i<nCategories; i++)
  867. {
  868. uroPlacesGroupsCollapsed.push(false);
  869. }
  870. }
  871.  
  872. for(i=0; i<nCategories; i++)
  873. {
  874. let parentCategory = W.Config.venues.categories[i];
  875. let localisedName = I18n.lookup("venues.categories." + parentCategory);
  876.  
  877. if(uroPlacesGroupsCollapsed[i] === true)
  878. {
  879. tHTML += '<i class="fa fa-plus-square-o" style="cursor:pointer;font-size:14px;" id="_uroPlacesGroupState-'+i+'"></i>';
  880. }
  881. else
  882. {
  883. tHTML += '<i class="fa fa-minus-square-o" style="cursor:pointer;font-size:14px;" id="_uroPlacesGroupState-'+i+'"></i>';
  884. }
  885.  
  886. tHTML += '&nbsp;<input type="checkbox" id="_cbPlacesFilter-'+parentCategory+'"><b>'+localisedName+'</b></input><br>';
  887. tHTML += '<div id="_uroPlacesGroup-'+i+'" style="padding:3px;border-width:2px;border-style:solid;border-color:#FFFFFF">';
  888.  
  889. for(let ii=0; ii<W.Config.venues.subcategories[parentCategory].length; ii++)
  890. {
  891. let subCategory = W.Config.venues.subcategories[parentCategory][ii];
  892. localisedName = I18n.lookup("venues.categories." + subCategory);
  893. tHTML += '&nbsp;&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbPlacesFilter-'+subCategory+'">'+localisedName+'</input><br>';
  894. }
  895. tHTML += '</div>';
  896. }
  897. tHTML += '<input type="checkbox" id="_cbFilterPrivatePlaces"><b>Residential Places</b></input><br>';
  898. tHTML += '<br><input type="checkbox" id="_cbInvertPlacesFilter">Invert Place filters?</input>';
  899.  
  900. return tHTML;
  901. },
  902. PopulateUR: function()
  903. {
  904. let iHTML = '';
  905. iHTML = '<br>';
  906.  
  907. iHTML += '<input type="checkbox" id="_cbURFilterOutsideArea">Hide URs outside my editable area</input><br>';
  908. iHTML += '<input type="checkbox" id="_cbURFilterInsideManagedAreas">Hide URs within AM areas</input>';
  909. iHTML += '&nbsp;(<input type="checkbox" id="_cbURExcludeUserArea">except my area)</input><br>';
  910. iHTML += '&nbsp;<i>Requires Area Manager layer to be enabled</i><br>';
  911. iHTML += '<input type="checkbox" id="_cbNoFilterForURInURL">Don\'t filter selected UR</input><br><br>';
  912. iHTML += '<input type="checkbox" id="_cbURFilterDupes">Show only duplicate URs</input><br><br>';
  913.  
  914. iHTML += '<b>Filter by type:</b><br>';
  915. iHTML += '<input type="checkbox" id="_cbFilterWazeAuto">Waze Automatic</input><br>';
  916. iHTML += '<input type="checkbox" id="_cbFilterIncorrectTurn">Incorrect turn</input><br>';
  917. iHTML += '<input type="checkbox" id="_cbFilterIncorrectAddress">Incorrect address</input><br>';
  918. iHTML += '<input type="checkbox" id="_cbFilterIncorrectRoute">Incorrect route</input><br>';
  919. iHTML += '<input type="checkbox" id="_cbFilterMissingRoundabout">Missing roundabout</input><br>';
  920. iHTML += '<input type="checkbox" id="_cbFilterGeneralError">General error</input><br>';
  921. iHTML += '<input type="checkbox" id="_cbFilterTurnNotAllowed">Turn not allowed</input><br>';
  922. iHTML += '<input type="checkbox" id="_cbFilterIncorrectJunction">Incorrect junction</input><br>';
  923. iHTML += '<input type="checkbox" id="_cbFilterMissingBridgeOverpass">Missing bridge overpass</input><br>';
  924. iHTML += '<input type="checkbox" id="_cbFilterWrongDrivingDirection">Wrong driving direction</input><br>';
  925. iHTML += '<input type="checkbox" id="_cbFilterMissingExit">Missing exit</input><br>';
  926. iHTML += '<input type="checkbox" id="_cbFilterMissingRoad">Missing road</input><br>';
  927. iHTML += '<input type="checkbox" id="_cbFilterBlockedRoad">Blocked road</input><br>';
  928. iHTML += '<input type="checkbox" id="_cbFilterMissingLandmark">Missing Landmark</input><br>';
  929. iHTML += '<input type="checkbox" id="_cbFilterSpeedLimits">Missing or Invalid Speed limit</input><br>';
  930. iHTML += '<input type="checkbox" id="_cbFilterUndefined">Undefined</input><br>';
  931.  
  932. iHTML += '&nbsp;&nbsp;<i>Specially tagged types</i><br>';
  933. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterRoadworks">[ROADWORKS]</input><br>';
  934. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterConstruction">[CONSTRUCTION]</input><br>';
  935. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterClosure">[CLOSURE]</input><br>';
  936. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterEvent">[EVENT]</input><br>';
  937. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterNote">[NOTE]</input><br>';
  938. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterBOG">[BOG]</input><br>';
  939. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterDifficult">[DIFFICULT]</input><br>';
  940. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbFilterWSLM">[WSLM]</input><br><br>';
  941. iHTML += '<input type="checkbox" id="_cbInvertURFilter">Invert operation of type filters?</input><br>';
  942.  
  943. iHTML += '<hr>';
  944.  
  945. iHTML += '<br><b>Hide by state:</b><br>';
  946. iHTML += '<input type="checkbox" id="_cbFilterOpenUR">Open</input><br>';
  947. iHTML += '<input type="checkbox" id="_cbFilterClosedUR">Closed</input><br>';
  948. iHTML += '<input type="checkbox" id="_cbFilterSolved">Solved</input><br>';
  949. iHTML += '<input type="checkbox" id="_cbFilterUnidentified">Not identified</input><br><br>';
  950.  
  951.  
  952. iHTML += '<br><b>Filter by age of submission:</b><br>';
  953. iHTML += '<input type="checkbox" id="_cbEnableMinAgeFilter">Hide URs less than </input>';
  954. iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMinDays"> days old<br>';
  955. iHTML += '<input type="checkbox" id="_cbEnableMaxAgeFilter">Hide URs more than </input>';
  956. iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMaxDays"> days old<br>';
  957.  
  958. iHTML += '<br><b>Filter by other details:</b><br>';
  959. iHTML += '<input type="checkbox" id="_cbHideMyFollowed" pairedWith="_cbHideMyUnfollowed">Hide</input> or ';
  960. iHTML += '<input type="checkbox" id="_cbHideMyUnfollowed" pairedWith="_cbHideMyFollowed">show</input> URs I\'m following<br><br>';
  961.  
  962. iHTML += '<input type="checkbox" id="_cbURDescriptionMustBePresent" pairedWith="_cbURDescriptionMustBeAbsent">Hide</input> or ';
  963. iHTML += '<input type="checkbox" id="_cbURDescriptionMustBeAbsent" pairedWith="_cbURDescriptionMustBePresent">show</input> URs with no description<br>';
  964. iHTML += '<input type="checkbox" id="_cbEnableKeywordMustBePresent">Hide URs not including </input>';
  965. iHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textKeywordPresent"><br>';
  966. iHTML += '<input type="checkbox" id="_cbEnableKeywordMustBeAbsent">Hide URs including </input>';
  967. iHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textKeywordAbsent"><br>';
  968. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbCaseInsensitive"><i>Case-insensitive matches?</i></input><br><br>';
  969.  
  970. iHTML += 'With comments from me?<br>';
  971. iHTML += '<input type="checkbox" id="_cbHideMyComments" pairedWith="_cbHideAnyComments">Yes </input>';
  972. iHTML += '<input type="checkbox" id="_cbHideAnyComments" pairedWith="_cbHideMyComments">No</input><br>';
  973. iHTML += 'If last comment made by me?<br>';
  974. iHTML += '<input type="checkbox" id="_cbHideIfLastCommenter" pairedWith="_cbHideIfNotLastCommenter">Yes </input>';
  975. iHTML += '<input type="checkbox" id="_cbHideIfNotLastCommenter" pairedWith="_cbHideIfLastCommenter">No </input><br>';
  976. iHTML += 'If last comment made by UR reporter?<br>';
  977. iHTML += '<input type="checkbox" id="_cbHideIfReporterLastCommenter" pairedWith="_cbHideIfReporterNotLastCommenter">Yes </input>';
  978. iHTML += '<input type="checkbox" id="_cbHideIfReporterNotLastCommenter" pairedWith="_cbHideIfReporterLastCommenter">No</input><br>';
  979.  
  980. iHTML += '<input type="checkbox" id="_cbEnableMinCommentsFilter">With less than </input>';
  981. iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMinComments"> comments<br>';
  982. iHTML += '<input type="checkbox" id="_cbEnableMaxCommentsFilter">With more than </input>';
  983. iHTML += '<input type="number" min="0" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMaxComments"> comments<br><br>';
  984.  
  985. iHTML += '<input type="checkbox" id="_cbEnableCommentAgeFilter2">Last comment less than </input>';
  986. iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterCommentDays2"> days ago<br>';
  987. iHTML += '<input type="checkbox" id="_cbEnableCommentAgeFilter">Last comment more than </input>';
  988. iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterCommentDays"> days ago<br>';
  989. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbIgnoreOtherEditorComments"><i>Ignore other editor comments?</i></input><br><br>';
  990.  
  991. iHTML += '<input type="checkbox" id="_cbURUserIDFilter">Without comments from user</input>';
  992. iHTML += '<select id="_selectURUserID" style="width:80%; height:22px;"></select><br>';
  993. iHTML += '<input type="checkbox" id="_cbURResolverIDFilter">Not resolved by user</input>';
  994. iHTML += '<select id="_selectURResolverID" style="width:80%; height:22px;"></select>';
  995.  
  996. iHTML += '<br><br><input type="checkbox" id="_cbInvertURStateFilter">Invert operation of state/age filters?</input><br>';
  997. iHTML += '<input type="checkbox" id="_cbNoFilterForTaggedURs">Don\'t apply state/age filters to tagged URs</input><br>';
  998.  
  999. return iHTML;
  1000. },
  1001. PopulateMC: function()
  1002. {
  1003. let iHTML = '';
  1004. iHTML = '<br>';
  1005.  
  1006. iHTML += '&nbsp;&nbsp;<i>Specially tagged types</i><br>';
  1007. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbMCFilterRoadworks">[ROADWORKS]</input><br>';
  1008. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbMCFilterConstruction">[CONSTRUCTION]</input><br>';
  1009. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbMCFilterClosure">[CLOSURE]</input><br>';
  1010. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbMCFilterEvent">[EVENT]</input><br>';
  1011. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbMCFilterNote">[NOTE]</input><br>';
  1012. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbMCFilterBOG">[BOG]</input><br>';
  1013. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbMCFilterDifficult">[DIFFICULT]</input><br>';
  1014. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbMCFilterWSLM">[WSLM]</input><br><br>';
  1015. iHTML += '<input type="checkbox" id="_cbInvertMCFilter">Invert operation of type filters?</input><br>';
  1016.  
  1017. iHTML += '<hr>';
  1018.  
  1019. iHTML += '<br><b>Filter by description/comments/following:</b><br>';
  1020. iHTML += '<input type="checkbox" id="_cbMCHideMyFollowed" pairedWith="_cbMCHideMyUnfollowed">Ones I am or </input>';
  1021. iHTML += '<input type="checkbox" id="_cbMCHideMyUnfollowed" pairedWith="_cbMCHideMyFollowed">am not following</input><br><br>';
  1022.  
  1023. iHTML += '<input type="checkbox" id="_cbMCDescriptionMustBePresent" pairedWith="_cbMCDescriptionMustBeAbsent">Hide</input> or ';
  1024. iHTML += '<input type="checkbox" id="_cbMCDescriptionMustBeAbsent" pairedWith="_cbMCDescriptionMustBePresent">show</input> MCs with no description<br>';
  1025. iHTML += '<input type="checkbox" id="_cbMCCommentsMustBePresent" pairedWith="_cbMCCommentsMustBeAbsent">Hide</input> or ';
  1026. iHTML += '<input type="checkbox" id="_cbMCCommentsMustBeAbsent" pairedWith="_cbMCCommentsMustBePresent">show</input> MCs with no comments<br>';
  1027. iHTML += '<input type="checkbox" id="_cbMCExpiryMustBePresent" pairedWith="_cbMCExpiryMustBeAbsent">Hide</input> or ';
  1028. iHTML += '<input type="checkbox" id="_cbMCExpiryMustBeAbsent" pairedWith="_cbMCExpiryMustBePresent">show</input> MCs with no expiry date<br>';
  1029. iHTML += '<input type="checkbox" id="_cbMCEnableKeywordMustBePresent">Hide MCs not including </input>';
  1030. iHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textMCKeywordPresent"><br>';
  1031. iHTML += '<input type="checkbox" id="_cbMCEnableKeywordMustBeAbsent">Hide MCs including </input>';
  1032. iHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textMCKeywordAbsent"><br>';
  1033. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbMCCaseInsensitive"><i>Case-insensitive matches?</i></input><br>';
  1034. iHTML += '<input type="checkbox" id="_cbMCCreatorIDFilter">Show MCs created by user</input>';
  1035. iHTML += '<select id="_selectMCCreatorID" style="width:80%; height:22px;"></select><br>';
  1036.  
  1037. iHTML += '<br><input type="checkbox" id="_cbHideWRCMCs"><b>Hide Waze_roadclosures MCs</b></input><br>';
  1038.  
  1039. iHTML += '<br><b>Hide MCs with lock level:</b><br>';
  1040. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideMCRank0">L1</input>';
  1041. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideMCRank1">L2</input>';
  1042. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideMCRank2">L3</input>';
  1043. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideMCRank3">L4</input>';
  1044. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideMCRank4">L5</input>';
  1045. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideMCRank5">L6</input>';
  1046. iHTML += '<hr>';
  1047. iHTML += '<input type="checkbox" id="_cbMCEnhancePointMCVisibility">Enhance visibility of point MCs</input>';
  1048.  
  1049. return iHTML;
  1050. },
  1051. PopulateCams: function()
  1052. {
  1053. let iHTML = '';
  1054. iHTML = '<br><b>Show Cameras created by:</b><br>';
  1055. iHTML += '<input type="checkbox" id="_cbShowWorldCams" checked>world_* users</input><br>';
  1056. iHTML += '<input type="checkbox" id="_cbShowUSACams" checked>usa_* users</input><br>';
  1057. iHTML += '<input type="checkbox" id="_cbShowNonWorldCams" checked>other users</input><br>';
  1058.  
  1059. iHTML += '<br><b>Show Cameras touched by a specific editor:</b><br>';
  1060. iHTML += '<input type="checkbox" id="_cbShowOnlyCamsCreatedBy">Created by</input>&nbsp;/&nbsp;';
  1061. iHTML += '<input type="checkbox" id="_cbShowOnlyCamsEditedBy">edited by</input><br>';
  1062. iHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textCameraEditor"><br>';
  1063. iHTML += '<select id="_selectCameraUserID" style="width:80%; height:22px;"></select><br>';
  1064. iHTML += '<br><input type="checkbox" id="_cbShowOnlyMyCams">Show ONLY cameras created/edited by me</input><br>';
  1065.  
  1066. iHTML += '<br><b>Show Cameras by type:</b><br>';
  1067. iHTML += '<input type="checkbox" id="_cbShowSpeedCams" checked>Speed</input><br>';
  1068. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbShowIfSpeedSet" checked> with speed data</input><br>';
  1069. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbShowIfNoSpeedSet" checked> with no speed data</input><br>';
  1070. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbShowIfInvalidSpeedSet" checked> with invalid speed data (zoom 16+)</input><br>';
  1071. iHTML += '&nbsp;&nbsp;&nbsp;<i>NOTE: slows down WME when deselected</i><br>';
  1072. iHTML += '<input type="checkbox" id="_cbShowRedLightCams" checked>Red Light</input><br>';
  1073. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbShowRLCIfZeroSpeedSet" checked> with speed limit = 0</input><br>';
  1074. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbShowRLCIfNonZeroSpeedSet" checked> with speed limit > 0</input><br>';
  1075. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbShowRLCIfNoSpeedSet" checked> with no speed data</input><br>';
  1076. iHTML += '<input type="checkbox" id="_cbShowDummyCams" checked>Dummy</input><br>';
  1077.  
  1078. iHTML += '<br><b>Hide Cameras by creator:</b><br>';
  1079. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideCreatedByMe">me</input>';
  1080. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideCreatedByRank0">L1</input>';
  1081. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideCreatedByRank1">L2</input>';
  1082. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideCreatedByRank2">L3</input>';
  1083. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideCreatedByRank3">L4</input>';
  1084. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideCreatedByRank4">L5</input>';
  1085. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideCreatedByRank5">L6</input>';
  1086.  
  1087. iHTML += '<br><b>Hide Cameras by updater:</b><br>';
  1088. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideUpdatedByMe">me</input>';
  1089. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideUpdatedByRank0">L1</input>';
  1090. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideUpdatedByRank1">L2</input>';
  1091. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideUpdatedByRank2">L3</input>';
  1092. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideUpdatedByRank3">L4</input>';
  1093. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideUpdatedByRank4">L5</input>';
  1094. iHTML += '&nbsp;&nbsp;&nbsp;<input type="checkbox" id="_cbHideUpdatedByRank5">L6</input>';
  1095.  
  1096. iHTML += '<br><br><b><input type="checkbox" id="_cbHideManualLockedCams">Show only auto-locked cameras</input></b><br>';
  1097.  
  1098. iHTML += '<br><b><input type="checkbox" id="_cbHideCWLCams">Hide cameras on watchlist</input></b><br>';
  1099.  
  1100. iHTML += '<br><b><input type="checkbox" id="_cbInvertCamFilters">Invert operation of camera filters?</input></b><br>';
  1101.  
  1102. iHTML += '<b><input type="checkbox" id="_cbHighlightInsteadOfHideCams">Highlight instead of hide</input></b><br>';
  1103. return iHTML;
  1104. },
  1105. PopulateRTC: function()
  1106. {
  1107. let iHTML = '';
  1108. iHTML = '<br><b>Hide Road Closures:</b><br>';
  1109. // Hidden checkbox to avoid errors when applying settings from previous versions of the script where this was an active control...
  1110. iHTML += '<input type="checkbox" id="_cbHideUserRTCs" style="display: none;" />';
  1111.  
  1112. iHTML += '<table style="text-align:center;">';
  1113. iHTML += '<tr><td/><td><div class="map-marker road-closure status-finished" style="margin-left:0px;margin-top:0px;" /></td><td><div class="map-marker road-closure status-active" style="margin-left:0px;margin-top:0px;" /></td><td><div class="map-marker road-closure status-not-started" style="margin-left:0px;margin-top:0px;" /></td><td>???</td></tr>';
  1114. iHTML += '<tr><td>From WME</td><td><input type="checkbox" id="_cbHideExpiredEditorRTCs" /></td><td><input type="checkbox" id="_cbHideEditorRTCs" /></td><td><input type="checkbox" id="_cbHideFutureEditorRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownEditorRTCs" /></td></tr>';
  1115. iHTML += '<tr><td>From WazeFeed</td><td><input type="checkbox" id="_cbHideExpiredWazeFeedRTCs" /></td><td><input type="checkbox" id="_cbHideWazeFeedRTCs" /></td><td><input type="checkbox" id="_cbHideFutureWazeFeedRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownWazeFeedRTCs" /></td></tr>';
  1116. iHTML += '<tr><td>From Staff</td><td><input type="checkbox" id="_cbHideExpiredWazeRTCs" /></td><td><input type="checkbox" id="_cbHideWazeRTCs" /></td><td><input type="checkbox" id="_cbHideFutureWazeRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownWazeRTCs" /></td></tr>';
  1117. iHTML += '<tr><td>In Sidepanel</td><td><input type="checkbox" id="_cbHideExpiredSidepanelRTCs" /></td><td><input type="checkbox" id="_cbHideSidepanelRTCs" /></td><td><input type="checkbox" id="_cbHideFutureSidepanelRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownSidepanelRTCs" /></td></tr>';
  1118. iHTML += '</table><br>';
  1119.  
  1120. iHTML += '<input type="checkbox" id="_cbShowMTERTCs" pairedWith="_cbHideMTERTCs">Show</input> or ';
  1121. iHTML += '<input type="checkbox" id="_cbHideMTERTCs" pairedWith="_cbShowMTERTCs">hide RTCs associated with MTE: </input>';
  1122. iHTML += '<select id="_selectRTCMTE" style="width:80%; height:22px;"></select><br>';
  1123. iHTML += '<br>';
  1124. iHTML += 'Hide if:<br>';
  1125. iHTML += '<input type="checkbox" id="_cbEnableRTCDurationFilterLessThan">Duration less than </input>';
  1126. iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterRTCDurationLessThan"> days<br>';
  1127. iHTML += '<input type="checkbox" id="_cbEnableRTCDurationFilterMoreThan">Duration more than </input>';
  1128. iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterRTCDurationMoreThan"> days<br>';
  1129. iHTML += '<br><b>Filter by date/time:</b><br>';
  1130. iHTML += '<input type="checkbox" id="_cbRTCFilterShowForTS" pairedWith="_cbRTCFilterHideForTS">Show</input> or ';
  1131. iHTML += '<input type="checkbox" id="_cbRTCFilterHideForTS" pairedWith="_cbRTCFilterShowForTS">hide</input> RTCs active at<br>';
  1132. iHTML += 'Date: <input type="number" min="1" max="31" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterDay"> / ';
  1133. iHTML += '<input type="number" min="1" max="12" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterMonth"> / ';
  1134. iHTML += '<input type="number" min="2010" max="2100" size="4" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterYear"><br>';
  1135. iHTML += 'Time: <input type="number" min="0" max="23" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterHour">:';
  1136. iHTML += '<input type="number" min="0" max="59" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterMin">';
  1137. return iHTML;
  1138. },
  1139. PopulateRA: function()
  1140. {
  1141. let iHTML = '';
  1142. iHTML = '<br><b>Filter Restricted Areas:</b><br>';
  1143. iHTML += '<input type="checkbox" id="_cbShowSpecificRA">Show a specific area: </input>';
  1144. iHTML += '<select id="_selectRA" style="width:80%; height:22px;"></select><br><br>';
  1145.  
  1146. iHTML += '<input type="checkbox" id="_cbRAEditorIDFilter">Show areas edited by user: </input>';
  1147. iHTML += '<select id="_selectRAEditorID" style="width:80%; height:22px;"></select><br><br>';
  1148.  
  1149. iHTML += 'Hide if:<br>';
  1150. iHTML += '<input type="checkbox" id="_cbEnableRAAgeFilterLessThan">Last modified less than </input>';
  1151. iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterRAAgeLessThan"> days ago<br>';
  1152. iHTML += '<input type="checkbox" id="_cbEnableRAAgeFilterMoreThan">Last modified more than </input>';
  1153. iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterRAAgeMoreThan"> days ago<br>';
  1154. return iHTML;
  1155. },
  1156. PopulateMisc: function()
  1157. {
  1158. let iHTML = '';
  1159. iHTML += '<br><b><input type="checkbox" id="_cbHideSegmentsWhenRoadsHidden" />Hide segment layer when road layer is hidden</b><br>';
  1160. iHTML += '<br><b><input type="checkbox" id="_cbKillInertialPanning" />Stop inertial panning when mouse moves out of map area</b><br>';
  1161.  
  1162. iHTML += '<br><br><b><input type="checkbox" id="_cbCommentCount" />Show comment count on UR markers</b><br>';
  1163.  
  1164. iHTML += '<br><br><b><input type="checkbox" id="_cbAutoApplyClonedClosure" />Auto-apply cloned closures</b><br>';
  1165. iHTML += '<b><input type="checkbox" id="_cbAutoScrollClosureList" />Auto-scroll to end of closures</b><br>';
  1166.  
  1167. iHTML += '<br><br><b>Disable filtering above zoom level </b>';
  1168. iHTML += '<input type="number" min="12" max="22" value="22" size="2" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMinZoomLevel" /><br>';
  1169.  
  1170. iHTML += '<br><br><b>Marker Unstacking:</b><br>';
  1171. iHTML += 'Distance threshold: <input type="number" min="1" max="30" value="15" size="2" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputUnstackSensitivity" /><br>';
  1172. iHTML += 'Disable below zoom: <input type="number" min="12" max="22" value="15" size="2" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputUnstackZoomLevel" /><br>';
  1173.  
  1174. iHTML += '<br><br><b>Popup mouse behaviour:</b><br>';
  1175. iHTML += 'Mouseover show delay <input type="number" min="1" max="10" value="2" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPopupEntryTimeout" /> *100ms<br>';
  1176. iHTML += 'Mouseout hide delay <input type="number" min="1" max="10" value="2" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPopupExitTimeout" /> *100ms<br>';
  1177. iHTML += 'Auto-hide after <input type="number" min="0" max="10" value="0" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPopupAutoHideTimeout" /> seconds<br>';
  1178.  
  1179. iHTML += '<br><br><b>Disable clustering for:</b><br>';
  1180. iHTML += '<input type="checkbox" id="_cbInhibitURClusters" />URs<br>';
  1181. iHTML += '<input type="checkbox" id="_cbInhibitMPClusters" />MPs<br>';
  1182. iHTML += '<input type="checkbox" id="_cbInhibitPUClusters" />PURs<br>';
  1183. iHTML += '<input type="checkbox" id="_cbInhibitESClusters" />ESs<br>';
  1184.  
  1185. iHTML += '<br><br><b>Disable popup for:</b><br>';
  1186. iHTML += '<input type="checkbox" id="_cbInhibitURPopup" />URs<br>';
  1187. iHTML += '<input type="checkbox" id="_cbInhibitMPPopup" />MPs<br>';
  1188. iHTML += '<input type="checkbox" id="_cbInhibitCamPopup" />Cameras<br>';
  1189. iHTML += '<input type="checkbox" id="_cbInhibitSegPopup" />Segments<br>';
  1190. iHTML += '&nbsp;&nbsp;<input type="checkbox" id="_cbInhibitSegGenericPopup" />Speed limit info<br>';
  1191. iHTML += '<input type="checkbox" id="_cbInhibitLandmarkPopup" />Places<br>';
  1192. iHTML += '<input type="checkbox" id="_cbInhibitPUPopup" />Place Updates<br>';
  1193. iHTML += '<input type="checkbox" id="_cbInhibitMapCommentPopup" />Map Comments<br>';
  1194. iHTML += '<input type="checkbox" id="_cbInhibitNodesPopup" />Junction Nodes<br>';
  1195.  
  1196. iHTML += '<br><br><b>Date/Time formatting for popups:</b><br>';
  1197. iHTML += '<input type="checkbox" id="_cbDateFmtDDMMYY" pairedWith="_cbDateFmtMMDDYY,_cbDateFmtYYMMDD" checked />day/month/year<br>';
  1198. iHTML += '<input type="checkbox" id="_cbDateFmtMMDDYY" pairedWith="_cbDateFmtDDMMYY,_cbDateFmtYYMMDD" />month/day/year<br>';
  1199. iHTML += '<input type="checkbox" id="_cbDateFmtYYMMDD" pairedWith="_cbDateFmtMMDDYY,_cbDateFmtDDMMYY" />year/month/day<br><br>';
  1200. iHTML += '<input type="checkbox" id="_cbTimeFmt24H" pairedWith="_cbTimeFmt12H" checked />24 hour<br>';
  1201. iHTML += '<input type="checkbox" id="_cbTimeFmt12H" pairedWith="_cbTimeFmt24H" />12 hour<br><br>';
  1202. iHTML += '<i>Unticked uses browser default setting</i>';
  1203.  
  1204. iHTML += '<br><br><b><input type="checkbox" id="_cbWhiteBackground" />Use custom background colour</b><br>';
  1205. iHTML += 'R:<input type="number" min="0" max="255" value="255" size="3" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputCustomBackgroundRed" />';
  1206. iHTML += 'G:<input type="number" min="0" max="255" value="255" size="3" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputCustomBackgroundGreen" />';
  1207. iHTML += 'B:<input type="number" min="0" max="255" value="255" size="3" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputCustomBackgroundBlue" /><br>';
  1208.  
  1209. iHTML += '<br><br><b>Replace "Next ..." button with "Done" for:</b><br>';
  1210. iHTML += '<input type="checkbox" id="_cbInhibitNURButton" />URs<br>';
  1211. iHTML += '<input type="checkbox" id="_cbInhibitNMPButton" />MPs<br>';
  1212. iHTML += '<input type="checkbox" id="_cbInhibitNPURButton" />PURs<br>';
  1213.  
  1214. iHTML += '<br><br><b>Disable on-click recentering for:</b><br>';
  1215. iHTML += '<input type="checkbox" id="_cbInhibitURCentering" />URs<br>';
  1216. iHTML += '<input type="checkbox" id="_cbInhibitMPCentering" />MPs<br>';
  1217. iHTML += '<input type="checkbox" id="_cbInhibitPURCentering" />PURs<br>';
  1218. iHTML += '<input type="checkbox" id="_cbInhibitPPURCentering" />PPURs<br>';
  1219. iHTML += '<input type="checkbox" id="_cbInhibitRPURCentering" />RPURs<br>';
  1220.  
  1221. iHTML += '<br><br><b><input type="checkbox" id="_cbHideAMLayer" />Hide Area Manager polygons</b><br>';
  1222. iHTML += '<b><input type="checkbox" id="_cbMoveAMList" />Show AMs in topbar when AM layer is active</b><br>';
  1223. iHTML += '<br><b><input type="checkbox" id="_cbDisablePlacesFiltering" />Disable Places filtering</b><br>';
  1224.  
  1225. iHTML += '<br><br><b>Settings backup/restore/reset:</b><br>';
  1226. iHTML += '<input type="button" id="_btnSettingsToText" value="Backup" />&nbsp;&nbsp;&nbsp;';
  1227. iHTML += '<input type="button" id="_btnTextToSettings" value="Restore" />&nbsp;&nbsp;|&nbsp;&nbsp;';
  1228. iHTML += '<input type="button" id="_btnResetSettings" value="Reset" /><br><br>';
  1229. iHTML += '<textarea id="_txtSettings" value=""></textarea><br>';
  1230. iHTML += '<input type="button" id="_btnClearSettingsText" value="Clear" /><br>';
  1231.  
  1232. /*
  1233. iHTML += '<br><br><b>Debug:</b><br>';
  1234. iHTML += '<input type="button" id="_btnDebugToScreen" value="Show debug data" />';
  1235. */
  1236. return iHTML;
  1237. },
  1238. PopulateOWL()
  1239. {
  1240. let camTypes = new Array("","","Speed", "Dummy", "Red Light");
  1241. let iHTML = '';
  1242. if(document.getElementById('_uroCWLGroupSelect') !== null)
  1243. {
  1244. uroTabs.selectedOWLGroup = document.getElementById('_uroCWLGroupSelect').selectedIndex;
  1245. }
  1246. iHTML = '<br><b>Camera Watchlist:</b><br><br>';
  1247. iHTML += '<div id="_uroCWLCamList" style="height:65%;overflow:auto;">';
  1248. if(uroOWL.CWLGroups.length > 0)
  1249. {
  1250. let camidx;
  1251. for(let groupidx=0;groupidx<uroOWL.CWLGroups.length;groupidx++)
  1252. {
  1253. let groupObj = uroOWL.CWLGroups[groupidx];
  1254. iHTML += '<div id="_uroCWLGroup-'+groupidx+'">';
  1255. if(groupObj.groupCollapsed === true)
  1256. {
  1257. iHTML += '<i class="fa fa-plus-square-o" style="cursor:pointer;font-size:14px;" id="_uroCWLGroupState-'+groupidx+'"></i>';
  1258. }
  1259. else
  1260. {
  1261. iHTML += '<i class="fa fa-minus-square-o" style="cursor:pointer;font-size:14px;" id="_uroCWLGroupState-'+groupidx+'"></i>';
  1262. }
  1263. iHTML += '<b>'+groupObj.groupName+'</b><br>';
  1264. groupObj.groupCount = 0;
  1265. if(uroOWL.CamWatchObjects.length > 0)
  1266. {
  1267. for(camidx=0;camidx<uroOWL.CamWatchObjects.length;camidx++)
  1268. {
  1269. let camObj = uroOWL.CamWatchObjects[camidx];
  1270. if(camObj.groupID == groupObj.groupID)
  1271. {
  1272. groupObj.groupCount++;
  1273. let changed = uroOWL.CamDataChanged(camidx);
  1274. let deleted = (camObj.loaded === false);
  1275. iHTML += '<div id="_uroCWL-'+camidx+'" style="padding:3px;border-width:2px;border-style:solid;border-color:#FFFFFF;background-color:';
  1276. if(camObj.server != W.app.getAppRegionCode())
  1277. {
  1278. if(camObj.server == '??') iHTML += '#A0A0A0;';
  1279. else iHTML += '#AAFFAA;';
  1280. }
  1281. else if(changed) iHTML += '#AAAAFF;';
  1282. else if(deleted) iHTML += '#FFAAAA;';
  1283. else iHTML += '#FFFFFF;';
  1284. if(groupObj.groupCollapsed === true) iHTML += 'display:none;">';
  1285. else iHTML += 'display:block;">';
  1286. iHTML += 'ID: '+camObj.fid;
  1287. iHTML += ' ('+camObj.server+')';
  1288. iHTML += ' Type: '+camTypes[camObj.watch.type];
  1289. if(camObj.server != W.app.getAppRegionCode())
  1290. {
  1291. if(camObj.server == '??')
  1292. {
  1293. iHTML += '<br><i>Unknown server</i>';
  1294. }
  1295. else
  1296. {
  1297. iHTML += '<br><i>Not on this server</i>';
  1298. }
  1299. }
  1300. else if(deleted)
  1301. {
  1302. iHTML += '<br>DELETED';
  1303. }
  1304. else if(changed)
  1305. {
  1306. if(camObj.current.type != camObj.watch.type)
  1307. {
  1308. iHTML += '<br>&nbsp;&nbsp;Type changed';
  1309. iHTML += ' ('+camObj.watch.type+' to '+camObj.current.type+')';
  1310. }
  1311. if(camObj.current.azymuth != camObj.watch.azymuth)
  1312. {
  1313. iHTML += '<br>&nbsp;&nbsp;Azimuth changed';
  1314. iHTML += ' ('+camObj.watch.azymuth+' to '+camObj.current.azymuth+')';
  1315. }
  1316. if(camObj.current.speed != camObj.watch.speed)
  1317. {
  1318. iHTML += '<br>&nbsp;&nbsp;Speed changed';
  1319. iHTML += ' ('+camObj.watch.speed+' to '+camObj.current.speed+')';
  1320. }
  1321. if(camObj.current.lat != camObj.watch.lat)
  1322. {
  1323. iHTML += '<br>&nbsp;&nbsp;Latitude changed';
  1324. iHTML += ' ('+camObj.watch.lat+' to '+camObj.current.lat+')';
  1325. }
  1326. if(camObj.current.lon != camObj.watch.lon)
  1327. {
  1328. iHTML += '<br>&nbsp;&nbsp;Longitude changed';
  1329. iHTML += ' ('+camObj.watch.lon+' to '+camObj.current.lon+')';
  1330. }
  1331. }
  1332. if(camObj.server == W.app.getAppRegionCode())
  1333. {
  1334. if(deleted === false)
  1335. {
  1336. iHTML += '&nbsp;<i class="fa fa-group" style="cursor:pointer;font-size:14px;color:#ccccff;" id="_uroCWLIcon1-'+camidx+'"></i>';
  1337. }
  1338. iHTML += '&nbsp;<i class="fa fa-arrow-circle-right" style="cursor:pointer;font-size:14px;color:#ccccff;" id="_uroCWLIcon2-'+camidx+'"></i>';
  1339. }
  1340. iHTML += '</div>';
  1341. }
  1342. }
  1343. }
  1344. iHTML += '</div>';
  1345. }
  1346. }
  1347. iHTML += '</div><div id="_uroCWLControls">';
  1348. iHTML += '<hr>Group control:<br>';
  1349. iHTML += '<select id="_uroCWLGroupSelect" style="width:40%;height:22px;"></select>&nbsp;<input type="button" id="_btnCWLGroupDel" value="Delete group"><br>';
  1350. iHTML += '<input type="text" id="_uroCWLGroupEntry" style="width:40%;height:22px;">&nbsp;<input type="button" id="_btnCWLGroupAdd" value="Add group">';
  1351. iHTML += '<br><input type="button" id="_btnRescanCamWatchList" value="Refresh camera data"><br><br>';
  1352. iHTML += '<input type="button" id="_btnUpdateCamValues" value="Accept all changes"><br><br>';
  1353. iHTML += '<b>Remove cameras from OWL:</b><br>';
  1354. iHTML += '<input type="button" id="_btnRemoveDeletedCameras" value="Deleted">&nbsp;&nbsp;';
  1355. iHTML += '<input type="button" id="_btnRemoveUnknownServerCameras" value="Unknown Server">&nbsp;&nbsp;';
  1356. iHTML += '<input type="button" id="_btnClearCamWatchList" value="ALL Cameras">';
  1357. iHTML += '</div>';
  1358. uroTabs.CtrlTabs[uroTabs.IDS.OWL][uroTabs.FIELDS.TABBODY].innerHTML = uroUtils.ModifyHTML(iHTML);
  1359. uroTabs.FinaliseOWLTab();
  1360. },
  1361. FinaliseOWLTab: function()
  1362. {
  1363. if(uroOWL.CamWatchObjects.length > 0)
  1364. {
  1365. if(document.getElementById("_uroCWL-0") == null)
  1366. {
  1367. window.setTimeout(uroTabs.FinaliseOWLTab,100);
  1368. return;
  1369. }
  1370. for(let camidx=0;camidx<uroOWL.CamWatchObjects.length;camidx++)
  1371. {
  1372. document.getElementById("_uroCWL-"+camidx).onmouseover = uroOWL.HighlightCWLEntry;
  1373. document.getElementById("_uroCWL-"+camidx).onmouseleave = uroOWL.UnhighlightCWLEntry;
  1374. if(uroOWL.CamWatchObjects[camidx].server == W.app.getAppRegionCode())
  1375. {
  1376. let icon1 = document.getElementById("_uroCWLIcon1-"+camidx);
  1377. let icon2 = document.getElementById("_uroCWLIcon2-"+camidx);
  1378. if(icon1 !== null)
  1379. {
  1380. icon1.onmouseover = uroOWL.CWLIconHighlight;
  1381. icon1.onmouseleave = uroOWL.CWLIconLowlight;
  1382. icon1.onclick = uroOWL.AssignCameraToGroup;
  1383. }
  1384. if(icon2 !== null)
  1385. {
  1386. icon2.onmouseover = uroOWL.CWLIconHighlight;
  1387. icon2.onmouseleave = uroOWL.CWLIconLowlight;
  1388. icon2.onclick = uroOWL.GotoCam;
  1389. }
  1390. }
  1391. }
  1392. }
  1393. if(document.getElementById('_btnClearCamWatchList') == null)
  1394. {
  1395. window.setTimeout(uroTabs.FinaliseOWLTab, 100);
  1396. return;
  1397. }
  1398. uroUtils.AddEventListener('_btnClearCamWatchList', 'click', uroOWL.ClearCamWatchList, true);
  1399. uroUtils.AddEventListener('_btnRemoveDeletedCameras', 'click', uroOWL.ClearDeletedCameras, true);
  1400. uroUtils.AddEventListener('_btnRemoveUnknownServerCameras', 'click', uroOWL.ClearUnknownServerCameras, true);
  1401. uroUtils.AddEventListener('_btnRescanCamWatchList', 'click', uroOWL.RescanCamWatchList, true);
  1402. uroUtils.AddEventListener('_btnUpdateCamValues', 'click', uroOWL.AcceptCameraChanges, true);
  1403. uroUtils.AddEventListener('_btnCWLGroupDel', 'click', uroOWL.RemoveCWLGroup, true);
  1404. uroUtils.AddEventListener('_btnCWLGroupAdd', 'click', uroOWL.AddCWLGroup, true);
  1405. if(document.getElementById('_uroCWLGroupSelect') !== null)
  1406. {
  1407. uroDBG.AddLog('populating CWL group list');
  1408. uroOWL.PopulateCWLGroupSelect();
  1409. let selector = document.getElementById('_uroCWLGroupSelect');
  1410. if(uroTabs.selectedOWLGroup >= selector.length)
  1411. {
  1412. uroTabs.selectedOWLGroup = 0;
  1413. }
  1414. selector.selectedIndex = uroTabs.selectedOWLGroup;
  1415. }
  1416. if(uroOWL.CWLGroups.length > 0)
  1417. {
  1418. for(let groupidx=0;groupidx<uroOWL.CWLGroups.length;groupidx++)
  1419. {
  1420. if(uroOWL.CWLGroups[groupidx].groupCount === 0)
  1421. {
  1422. uroTabs.SetStyleDisplay('_uroCWLGroup-'+groupidx,'none');
  1423. }
  1424. else
  1425. {
  1426. uroUtils.SetOnClick('_uroCWLGroupState-'+groupidx,uroOWL.CWLGroupCollapseExpand);
  1427. }
  1428. }
  1429. }
  1430. },
  1431. ActiveTab: function(_id)
  1432. {
  1433. let e = document.getElementById(_id);
  1434. e.style.backgroundColor = "greenyellow";
  1435. e.style.borderTop = "1px solid";
  1436. e.style.borderLeft = "1px solid";
  1437. e.style.borderRight = "1px solid";
  1438. e.style.borderBottom = "0px solid";
  1439. },
  1440. InactiveTab: function(_id)
  1441. {
  1442. let e = document.getElementById(_id);
  1443. e.style.backgroundColor = "gainsboro";
  1444. e.style.borderTop = "0px solid";
  1445. e.style.borderLeft = "0px solid";
  1446. e.style.borderRight = "0px solid";
  1447. e.style.borderBottom = "1px solid";
  1448. },
  1449. InactiveAllTabs: function()
  1450. {
  1451. for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
  1452. {
  1453. uroTabs.InactiveTab(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABHEADER]);
  1454. uroTabs.SetStyleDisplay(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY], 'none');
  1455. }
  1456. },
  1457. ShowTab: function(tabID)
  1458. {
  1459. uroTabs.InactiveAllTabs();
  1460. uroTabs.ActiveTab(uroTabs.CtrlTabs[tabID][uroTabs.FIELDS.TABHEADER]);
  1461. uroTabs.SetStyleDisplay(uroTabs.CtrlTabs[tabID][uroTabs.FIELDS.TABBODY], 'block');
  1462. return false;
  1463. },
  1464. ShowURs: function()
  1465. {
  1466. uroTabs.ShowTab(uroTabs.IDS.URS);
  1467. return false;
  1468. },
  1469. ShowMPs: function()
  1470. {
  1471. uroTabs.ShowTab(uroTabs.IDS.MPS);
  1472. return false;
  1473. },
  1474. ShowMCs: function()
  1475. {
  1476. uroTabs.ShowTab(uroTabs.IDS.MCS);
  1477. return false;
  1478. },
  1479. ShowPlaces: function()
  1480. {
  1481. uroTabs.ShowTab(uroTabs.IDS.PLACES);
  1482. for(let idx=0;idx<uroPlacesGroupsCollapsed.length;idx++)
  1483. {
  1484. uroPlacesGroupCEHandler(idx);
  1485. }
  1486. return false;
  1487. },
  1488. ShowCams: function()
  1489. {
  1490. uroTabs.ShowTab(uroTabs.IDS.CAMS);
  1491. return false;
  1492. },
  1493. ShowOWL: function()
  1494. {
  1495. uroTabs.ShowTab(uroTabs.IDS.OWL);
  1496. uroTabs.PopulateOWL();
  1497. return false;
  1498. },
  1499. ShowMisc: function()
  1500. {
  1501. uroTabs.ShowTab(uroTabs.IDS.MISC);
  1502. return false;
  1503. },
  1504. ShowRTCs: function()
  1505. {
  1506. uroTabs.ShowTab(uroTabs.IDS.RTCS);
  1507. return false;
  1508. },
  1509. ShowRAs: function()
  1510. {
  1511. uroTabs.ShowTab(uroTabs.IDS.RAS);
  1512. return false;
  1513. },
  1514. ClickURs: function()
  1515. {
  1516. uroFilterURs();
  1517. },
  1518. ClickMPs: function()
  1519. {
  1520. uroFilterProblems();
  1521. },
  1522. ClickMCs: function()
  1523. {
  1524. uroFilterMapComments();
  1525. uroLayers.MCLayerChanged();
  1526. },
  1527. ClickPlaces: function()
  1528. {
  1529. uroFilterPlaces();
  1530. },
  1531. ClickCams: function()
  1532. {
  1533. uroFilterCameras();
  1534. },
  1535. ClickOWL: function()
  1536. {
  1537. // no action required
  1538. },
  1539. ClickMisc: function()
  1540. {
  1541. uroFilterItems();
  1542. uroUITweaks.ChangeMapBGColour();
  1543. uroUITweaks.HideAMLayer();
  1544. uroUITweaks.HideSegments();
  1545. uroUITweaks.ChangeClustering();
  1546. },
  1547. ClickRTCs: function()
  1548. {
  1549. uroFilterRTCs();
  1550. uroClosureListHandler();
  1551. },
  1552. ClickRAs: function()
  1553. {
  1554. uroFilterRAs();
  1555. },
  1556. CreateTabHeaders: function()
  1557. {
  1558. uroTabs.CtrlTabs =
  1559. [
  1560. ['_tabURs', null, '_linkURs', 'URs', uroTabs.ShowURs, uroTabs.ClickURs, 'UROverviewUROptions', uroTabs.PopulateUR],
  1561. ['_tabMPs', null, '_linkMPs', 'MPs', uroTabs.ShowMPs, uroTabs.ClickMPs, 'UROverviewMPOptions', uroTabs.PopulateMP],
  1562. ['_tabMCs', null, '_linkMCs', 'MCs', uroTabs.ShowMCs, uroTabs.ClickMCs, 'UROverviewMCOptions', uroTabs.PopulateMC],
  1563. ['_tabRTCs', null, '_linkRTCs', 'RTCs', uroTabs.ShowRTCs, uroTabs.ClickRTCs, 'UROverviewRTCOptions', uroTabs.PopulateRTC],
  1564. ['_tabRAs', null, '_linkRAs', 'RAs', uroTabs.ShowRAs, uroTabs.ClickRAs, 'UROverviewRAOptions', uroTabs.PopulateRA],
  1565. ['_tabPlaces', null, '_linkPlaces', 'Places', uroTabs.ShowPlaces, uroTabs.ClickPlaces, 'UROverviewPlacesOptions', uroTabs.PopulatePlaces],
  1566. ['_tabCams', null, '_linkCams', 'Cams', uroTabs.ShowCams, uroTabs.ClickCams, 'UROverviewCameraOptions', uroTabs.PopulateCams],
  1567. ['_tabOWL', null, '_linkOWL', 'OWL', uroTabs.ShowOWL, uroTabs.ClickOWL, null, null],
  1568. ['_tabMisc', null, '_linkMisc', 'Misc', uroTabs.ShowMisc, uroTabs.ClickMisc, 'UROverviewMiscOptions', uroTabs.PopulateMisc]
  1569. ];
  1570.  
  1571. let i;
  1572. let tabsTotal = uroTabs.CtrlTabs.length;
  1573. let tabsPerRow = Math.ceil(tabsTotal / Math.ceil(tabsTotal / uroTabs.MAX_PER_ROW));
  1574. let tabCount = 0;
  1575. let tabbyHTML = '';
  1576. for(i = 0; i < tabsTotal; ++i)
  1577. {
  1578. if(tabCount == 0)
  1579. {
  1580. tabbyHTML += '<table border=0 width="100%"><tr>';
  1581. }
  1582. tabbyHTML += '<td valign="center" align="center" id="'+uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABHEADER]+'">';
  1583. tabbyHTML += '<a href="#" id="'+uroTabs.CtrlTabs[i][uroTabs.FIELDS.LINKID]+'" style="text-decoration:none;font-size:12px">'+uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABTITLE]+'</a></td>';
  1584. if(((++tabCount == tabsPerRow) && (i < (tabsTotal - 1))) || (i == (tabsTotal - 1)))
  1585. {
  1586. tabbyHTML += '</tr></table>';
  1587. tabCount = 0;
  1588. }
  1589. }
  1590. return tabbyHTML;
  1591. },
  1592. CreateTabBodies: function()
  1593. {
  1594. for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
  1595. {
  1596. uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY] = document.createElement('div');
  1597. }
  1598. },
  1599. AddToDOM: function()
  1600. {
  1601. let i;
  1602. for(i = 0; i < uroTabs.CtrlTabs.length; ++i)
  1603. {
  1604. if(uroTabs.CtrlTabs[i][uroTabs.FIELDS.POPULATEFN] != null)
  1605. {
  1606. uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY].innerHTML = uroTabs.CtrlTabs[i][uroTabs.FIELDS.POPULATEFN]();
  1607. }
  1608. document.getElementById('uroControlsContainer').appendChild(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY]);
  1609. uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY].onclick = uroTabs.CtrlTabs[i][uroTabs.FIELDS.CLICKFN];
  1610. }
  1611. }
  1612. };
  1613. const uroDBG = // debug output handling
  1614. {
  1615. // true enables debug output during script startup
  1616. showDebugOutput: true,
  1617. // true keeps debug output enabled after script startup
  1618. persistentDebugOutput: false,
  1619. // true enables performance monitoring debug output
  1620. performanceMonitoringOutput: false,
  1621.  
  1622. recentDebug: [],
  1623.  
  1624. Add: function(debugtext)
  1625. {
  1626. let ts = Math.round(performance.now());
  1627. if(uroDBG.recentDebug.length == 100)
  1628. {
  1629. uroDBG.recentDebug.shift();
  1630. }
  1631. uroDBG.recentDebug.push(ts+': '+debugtext);
  1632. console.debug('URO+DBG '+ts+':'+debugtext);
  1633. },
  1634. Dump: function()
  1635. {
  1636. if(uroDBG.recentDebug.length > 0)
  1637. {
  1638. document.getElementById('WazeMap').innerHTML = uroUtils.ModifyHTML('<textarea id="uroDbgOutput" style="width:100%;height:100%">');
  1639. let dbgOutput = '';
  1640. for(let i = 0; i < uroDBG.recentDebug.length; i++)
  1641. {
  1642. dbgOutput += uroDBG.recentDebug[i]+'\n';
  1643. }
  1644. document.getElementById('uroDbgOutput').textContent = dbgOutput;
  1645. }
  1646. },
  1647. Toggle: function()
  1648. {
  1649. uroDBG.showDebugOutput = !uroDBG.showDebugOutput;
  1650. let dbgMode = "none";
  1651. if(uroDBG.showDebugOutput)
  1652. {
  1653. dbgMode = "inline";
  1654. }
  1655. document.getElementById('_uroDebugMode').style.display = dbgMode;
  1656. },
  1657. PerfMon: function(source, ts)
  1658. {
  1659. if(uroDBG.performanceMonitoringOutput === true)
  1660. {
  1661. console.log(source+': '+(performance.now() - ts));
  1662. }
  1663. },
  1664. AddLog: function(logtext)
  1665. {
  1666. if(uroDBG.showDebugOutput) console.log('URO+: '+Date()+' '+logtext);
  1667. }
  1668. };
  1669. const uroAFN = // area friendly name functions
  1670. {
  1671. friendlyNames: [],
  1672. hoverTime: -1,
  1673. hoverObj: null,
  1674. overlayShown: false,
  1675. editHovered: false,
  1676. editBox: null,
  1677. friendlyAreaNames: null,
  1678. AFNObject: function(fName, area, server)
  1679. {
  1680. this.fName = fName;
  1681. this.area = area;
  1682. this.server = server;
  1683. },
  1684. UpdateName: function(name, server, area)
  1685. {
  1686. let foundExisting = false;
  1687. for(let i = 0; i < uroAFN.friendlyNames.length; i++)
  1688. {
  1689. if((uroAFN.friendlyNames[i].server == server) && (uroAFN.friendlyNames[i].area == area))
  1690. {
  1691. if(name === "")
  1692. {
  1693. this.friendlyNames.splice(i, 1);
  1694. foundExisting = true;
  1695. }
  1696. else
  1697. {
  1698. uroAFN.friendlyNames[i].fName = name;
  1699. foundExisting = true;
  1700. }
  1701. }
  1702. }
  1703. if((foundExisting === false) && (name !== ""))
  1704. {
  1705. uroAFN.friendlyNames.push(new uroAFN.AFNObject(name, area, server));
  1706. }
  1707. uroAFN.ReplaceAreaNames(true);
  1708. },
  1709. AreaNameHover: function()
  1710. {
  1711. if((uroAFN.hoverObj === null) || (uroAFN.hoverObj != this))
  1712. {
  1713. uroAFN.hoverTime = 0;
  1714. }
  1715. uroAFN.hoverObj = this;
  1716. },
  1717. AreaNameUnHover: function()
  1718. {
  1719. if(uroAFN.editHovered === true)
  1720. {
  1721. return false;
  1722. }
  1723. if(uroAFN.overlayShown)
  1724. {
  1725. uroAFN.hoverObj.removeChild(uroAFN.editBox);
  1726. }
  1727. uroAFN.hoverObj = null;
  1728. uroAFN.hoverTime = -1;
  1729. uroAFN.overlayShown = false;
  1730. },
  1731. EditHover: function()
  1732. {
  1733. uroAFN.editHovered = true;
  1734. uroUtils.AddEventListener('uroANEditBox', 'mouseout', uroAFN.EditUnHover, false);
  1735. uroUtils.AddEventListener('uroANEditBox', 'click', uroAFN.EditClick, false);
  1736. },
  1737. EditUnHover: function()
  1738. {
  1739. let newName = document.getElementById('_textAreaName').value;
  1740. // sanitise name to avoid conflicts with config storage delimiters...
  1741. newName = newName.replace(',','');
  1742. newName = newName.replace(':','');
  1743. let server = W.app.getAppRegionCode();
  1744. let area = uroAFN.GetAreaArea(uroAFN.hoverObj.parentNode.children[1].innerText);
  1745. uroAFN.hoverObj.removeChild(uroAFN.editBox);
  1746. uroAFN.overlayShown = false;
  1747. uroAFN.editHovered = false;
  1748. uroAFN.UpdateName(newName, server, area);
  1749. },
  1750. EditClick: function(e)
  1751. {
  1752. // this traps the click to prevent it falling through to the underlying area name element and
  1753. // potentially causing the map view to be relocated to that area...
  1754. e.stopPropagation();
  1755. },
  1756. GetAreaArea: function(area)
  1757. {
  1758. area = parseFloat(area.split(' ')[0]);
  1759. return area;
  1760. },
  1761. OverlaySetup: function()
  1762. {
  1763. uroAFN.overlayShown = true;
  1764. uroAFN.editBox = document.createElement('div');
  1765. uroAFN.editBox.id = "uroANEditBox";
  1766. uroAFN.editBox.style.position = "absolute";
  1767. uroAFN.editBox.style.top = '0px';
  1768. uroAFN.editBox.style.left = '0px';
  1769. uroAFN.editBox.style.width = "99%";
  1770. uroAFN.hoverObj.appendChild(uroAFN.editBox);
  1771. uroAFN.editBox.onmouseover = uroAFN.EditHover();
  1772. let existingName = uroAFN.hoverObj.innerHTML;
  1773. let italicTagPos = existingName.indexOf(' <i>');
  1774. if(italicTagPos == -1)
  1775. {
  1776. existingName = "";
  1777. }
  1778. else
  1779. {
  1780. existingName = existingName.substr(0,italicTagPos);
  1781. }
  1782. uroAFN.editBox.innerHTML = uroUtils.ModifyHTML('<input type="text" style="font-size:14px; line-height:16px; height:22px; width:100%" id="_textAreaName" value="'+existingName+'">');
  1783. },
  1784. ReplaceAreaNames: function(replaceAfterNameChange)
  1785. {
  1786. if(document.getElementById('sidepanel-areas') === undefined)
  1787. {
  1788. return;
  1789. }
  1790. if(document.getElementById('sidepanel-areas').getElementsByClassName('result-list').length === 0)
  1791. {
  1792. return;
  1793. }
  1794. if(replaceAfterNameChange === false)
  1795. {
  1796. if(document.getElementById('sidepanel-areas').getElementsByClassName('result-list')[0].id == "friendlyNamed")
  1797. {
  1798. return;
  1799. }
  1800. }
  1801. let panelRootObj = document.getElementById('sidepanel-areas').getElementsByClassName('result-list')[0];
  1802. if(panelRootObj === undefined)
  1803. {
  1804. // we get here if the user doesn't have any areas defined...
  1805. return;
  1806. }
  1807. let areaNameObjs = panelRootObj.getElementsByClassName('list-item-card-info');
  1808. if(areaNameObjs.length === 0)
  1809. {
  1810. return;
  1811. }
  1812. let localisedManagedArea = I18n.lookup("user.areas.managed_area");
  1813. for(let loop=0; loop < areaNameObjs.length; loop++)
  1814. {
  1815. if(areaNameObjs[loop].children.length === 2)
  1816. {
  1817. let title = areaNameObjs[loop].children[0].innerText;
  1818. if(title.indexOf(localisedManagedArea) > -1)
  1819. {
  1820. let area = uroAFN.GetAreaArea(areaNameObjs[loop].children[1].innerText);
  1821. areaNameObjs[loop].children[0].innerHTML = uroUtils.ModifyHTML(localisedManagedArea);
  1822. for(let fnIdx=0; fnIdx < uroAFN.friendlyNames.length; fnIdx++)
  1823. {
  1824. let fnObj = uroAFN.friendlyNames[fnIdx];
  1825. if((fnObj.area == area) && (fnObj.server == W.app.getAppRegionCode()))
  1826. {
  1827. areaNameObjs[loop].children[0].innerHTML = uroUtils.ModifyHTML(fnObj.fName +' <i>('+localisedManagedArea+')</i>');
  1828. break;
  1829. }
  1830. }
  1831. let titleObj = areaNameObjs[loop].getElementsByClassName('list-item-card-title')[0];
  1832. titleObj.addEventListener("mouseover", uroAFN.AreaNameHover, false);
  1833. titleObj.addEventListener("mouseout", uroAFN.AreaNameUnHover, false);
  1834. titleObj.style.cursor = "text";
  1835. }
  1836. }
  1837. }
  1838. document.getElementById('sidepanel-areas').getElementsByClassName('result-list')[0].id = "friendlyNamed";
  1839. },
  1840. ApplyNames: function()
  1841. {
  1842. let objects = localStorage.UROverviewFriendlyAreaNames.split(':');
  1843. uroAFN.friendlyNames = [];
  1844. for(let objIdx=0;objIdx<objects.length;objIdx++)
  1845. {
  1846. let fields = objects[objIdx].split(',');
  1847. uroAFN.friendlyNames.push(new uroAFN.AFNObject(fields[0],parseFloat(fields[1]),fields[2]));
  1848. }
  1849. uroAFN.ReplaceAreaNames(true);
  1850. }
  1851. };
  1852. const uroMarkers = // marker-related function
  1853. {
  1854. elm : null,
  1855. obj : null,
  1856. id : null,
  1857. type : null,
  1858. lastOver : null,
  1859.  
  1860. mouseX : null,
  1861. mouseY : null,
  1862. mouseButtons : null,
  1863. clientX : null,
  1864. clientY : null,
  1865. armHover : false,
  1866. entryTimeout : null,
  1867. inhibitSetCenter : false,
  1868. clickedOnCenter : null,
  1869. clickedOnID : null,
  1870.  
  1871. EntryTimeout: function()
  1872. {
  1873. uroMarkers.armHover = false;
  1874. if(uroMarkers.lastOver !== null)
  1875. {
  1876. if(uroMarkers.type === 'cam')
  1877. {
  1878. if(uroUtils.GetCBChecked('_cbHighlightInsteadOfHideCams') === true)
  1879. {
  1880. if(uroMarkers.lastOver !== uroMarkers.id)
  1881. {
  1882. window.setTimeout(uroFilterCameras, 50);
  1883. }
  1884. }
  1885. }
  1886. else if((uroMarkers.type == uroLayers.ID.UR) || (uroMarkers.type == uroLayers.ID.MP))
  1887. {
  1888. if(uroMarkers.type == uroLayers.ID.UR) uroHoveredURID = uroMarkers.id;
  1889. }
  1890.  
  1891. uroDBG.AddLog('hover over marker (Type ' + uroMarkers.type + ' / ID ' + uroMarkers.id + ')');
  1892. uroPopup.Generate();
  1893. }
  1894. },
  1895. MouseMove: function(e)
  1896. {
  1897. uroMarkers.buttons = e.buttons;
  1898. uroMarkers.mouseX = e.pageX - document.getElementById('map').getBoundingClientRect().left;
  1899. uroMarkers.mouseY = e.pageY - document.getElementById('map').getBoundingClientRect().top;
  1900. uroMarkers.clientX = e.clientX;
  1901. uroMarkers.clientY = e.clientY;
  1902.  
  1903. if(uroMarkers.armHover === true)
  1904. {
  1905. let eto = uroUtils.GetElmValue('_inputPopupEntryTimeout') * 100;
  1906. if(uroMarkers.entryTimeout !== null)
  1907. {
  1908. window.clearTimeout(uroMarkers.entryTimeout);
  1909. }
  1910. uroMarkers.entryTimeout = window.setTimeout(uroMarkers.EntryTimeout, eto);
  1911. }
  1912. },
  1913. TranslateType: function(ft)
  1914. {
  1915. const TLU =
  1916. [
  1917. ["mapProblem", uroLayers.ID.MP],
  1918. ["mapUpdateRequest", uroLayers.ID.UR],
  1919. ["placeUpdate", uroLayers.ID.PUR],
  1920. ["segmentSuggestionGeoIcon", uroLayers.ID.SegSug],
  1921. ["camera", "cam"],
  1922. ["node", "node"],
  1923. ["comment", "comment"],
  1924. ["venue", "venue"],
  1925. ["segment", "segment"]
  1926. ];
  1927.  
  1928. let retval = null;
  1929. for(let i = 0; i < TLU.length; ++i)
  1930. {
  1931. if(ft == TLU[i][0])
  1932. {
  1933. retval = TLU[i][1];
  1934. break;
  1935. }
  1936. }
  1937. return retval;
  1938. },
  1939. MouseOver: function(e)
  1940. {
  1941. let elm = null;
  1942. let obj = null;
  1943. let id = null;
  1944. let markerType = null;
  1945. let ft = e?.feature?.attributes?.wazeFeature?.featureType;
  1946. if(ft !== undefined)
  1947. {
  1948. // single marker...
  1949. obj = e.feature.attributes.wazeFeature._wmeObject;
  1950. elm = W.userscripts.getMapElementByDataModel(obj);
  1951. id = e.feature.attributes.wazeFeature.id;
  1952. markerType = uroMarkers.TranslateType(ft);
  1953.  
  1954. uroMarkers.elm = elm;
  1955. uroMarkers.obj = obj;
  1956. uroMarkers.id = id;
  1957. uroMarkers.type = markerType;
  1958. uroMarkers.lastOver = id;
  1959. uroMarkers.armHover = true;
  1960.  
  1961. uroMarkers.AddMarkerEventHandler();
  1962. }
  1963. /*
  1964. else
  1965. {
  1966. if(e?.feature?.cluster !== undefined)
  1967. {
  1968. // cluster marker...
  1969. let cm = e.feature.cluster;
  1970. if(cm.length > 0)
  1971. {
  1972. // clusters are always of the same type, so just need to check the
  1973. // first marker within the cluster to see what the type is for all
  1974. // of them...
  1975. ft = cm[0].attributes.wazeFeature.featureType;
  1976. markerType = uroMarkers.TranslateType(ft);
  1977.  
  1978. console.debug("Cluster Marker:");
  1979. console.debug(" Type = " + ft);
  1980. console.debug(" Cluster size = " + cm.length);
  1981. console.debug(e);
  1982. }
  1983. }
  1984. }
  1985. */
  1986. },
  1987. MouseOver2: function(e)
  1988. {
  1989. let elm = null;
  1990. let obj = null;
  1991. let id = null;
  1992. let markerType = null;
  1993. let ft = e?.currentTarget?.attributes?.class?.value;
  1994. if(ft !== undefined)
  1995. {
  1996. elm = e.currentTarget;
  1997. if(ft.indexOf("permanentHazardMarker") !== -1)
  1998. {
  1999. elm = elm.parentNode;
  2000. markerType = "phCam";
  2001. }
  2002. obj = W.userscripts.getDataModelByMapElement(elm);
  2003. id = elm.attributes["data-id"].value;
  2004.  
  2005. console.debug(ft);
  2006. console.debug(id);
  2007.  
  2008. uroMarkers.elm = elm;
  2009. uroMarkers.obj = obj;
  2010. uroMarkers.id = id;
  2011. uroMarkers.type = markerType;
  2012. uroMarkers.lastOver = id;
  2013. uroMarkers.armHover = true;
  2014. }
  2015. },
  2016. MouseOut: function()
  2017. {
  2018. uroMarkers.lastOver = null;
  2019. if(uroMarkers.type !== null)
  2020. {
  2021. if(uroMarkers.type === 'cam')
  2022. {
  2023. if(uroUtils.GetCBChecked('_cbHighlightInsteadOfHideCams') === true)
  2024. {
  2025. window.setTimeout(uroFilterCameras, 50);
  2026. }
  2027. }
  2028. uroDBG.AddLog('hover off '+uroMarkers.type+' ID '+uroMarkers.id);
  2029. uroHoveredURID = null;
  2030.  
  2031. uroFID = -1;
  2032. if(uroStackType !== null)
  2033. {
  2034. let tStackType = uroStackType;
  2035. uroRestackMarkers();
  2036. if(tStackType == 1)
  2037. {
  2038. uroFilterURs();
  2039. }
  2040. else if(tStackType == 2)
  2041. {
  2042. uroFilterProblems();
  2043. }
  2044. else if(tStackType == 3)
  2045. {
  2046. uroFilterPlaces();
  2047. }
  2048. }
  2049.  
  2050. if(uroPopup.timer == -1)
  2051. {
  2052. uroPopup.timer = uroUtils.GetElmValue('_inputPopupExitTimeout');
  2053. }
  2054. }
  2055. else
  2056. {
  2057. uroDBG.AddLog('hover off unknown object...');
  2058. }
  2059. },
  2060. MouseOut2: function()
  2061. {
  2062. uroMarkers.lastOver = null;
  2063. if(uroMarkers.type !== null)
  2064. {
  2065. uroDBG.AddLog('hover off '+uroMarkers.type+' ID '+uroMarkers.id);
  2066. uroHoveredURID = null;
  2067. uroFID = -1;
  2068. if(uroPopup.timer == -1)
  2069. {
  2070. uroPopup.timer = uroUtils.GetElmValue('_inputPopupExitTimeout');
  2071. }
  2072. }
  2073. else
  2074. {
  2075. uroDBG.AddLog('hover off unknown object...');
  2076. }
  2077. },
  2078. MouseDown: function()
  2079. {
  2080. // Do this stuff in the mousedown event rather than the click event so we fire before any of the native
  2081. // click events - we need to ensure this happens for inhibiting marker centering, as we need to capture
  2082. // the markerType ahead of our interceptor function being called to deal with the centering...
  2083. if(uroMarkers.type !== null)
  2084. {
  2085. uroDBG.AddLog('clicked on '+uroMarkers.type+' marker '+uroMarkers.id);
  2086. uroMarkers.clickedOnID = uroMarkers.id;
  2087. uroMarkers.clickedOnCenter = W.map.getCenter();
  2088. uroInhibitURFiltering = true;
  2089. if(uroMarkers.inhibitSetCenter === false)
  2090. {
  2091. if(uroMarkers.Decentre() === true)
  2092. {
  2093. uroMarkers.inhibitSetCenter = true;
  2094. }
  2095. }
  2096. }
  2097. },
  2098. Decentre: function()
  2099. {
  2100. let inhibit = false;
  2101. inhibit = inhibit || ((uroMarkers.type == uroLayers.ID.UR) && (uroUtils.GetCBChecked("_cbInhibitURCentering")));
  2102. inhibit = inhibit || ((uroMarkers.type == uroLayers.ID.MP) && (uroUtils.GetCBChecked("_cbInhibitMPCentering")));
  2103. inhibit = inhibit || ((uroMarkers.type == uroLayers.ID.PUR) && (uroUtils.GetCBChecked("_cbInhibitPURCentering")));
  2104. inhibit = inhibit || ((uroMarkers.type == uroLayers.ID.PPUR) && (uroUtils.GetCBChecked("_cbInhibitPPURCentering")));
  2105. inhibit = inhibit || ((uroMarkers.type == uroLayers.ID.RPUR) && (uroUtils.GetCBChecked("_cbInhibitRPURCentering")));
  2106. return inhibit;
  2107. },
  2108. RegisterEvents: function()
  2109. {
  2110. for(let i = 0; i < uroLayers.layers.length; ++i)
  2111. {
  2112. if(uroLayers.layers[i].regEvt === true)
  2113. {
  2114. if((uroLayers.layers[i].isFeature === true) || (uroLayers.layers[i].isFeature === null))
  2115. {
  2116. uroLayers.layers[i].l.events.register("fe-feature-in", null, uroMarkers.MouseOver);
  2117. uroLayers.layers[i].l.events.register("fe-feature-out", null, uroMarkers.MouseOut);
  2118. }
  2119. else if(uroLayers.layers[i].isFeature === false)
  2120. {
  2121. for(let j = 0; j < uroLayers.layers[i].mf.length; ++j)
  2122. {
  2123. let mMarker = uroLayers.layers[i].mf[j];
  2124. if(mMarker !== null)
  2125. {
  2126. let mIcon = null;
  2127. if(mMarker.element !== undefined)
  2128. {
  2129. if(uroLayers.layers[i].moChild === true)
  2130. {
  2131. mIcon = mMarker.element.firstChild;
  2132. }
  2133. else
  2134. {
  2135. mIcon = mMarker.element;
  2136. }
  2137. }
  2138. else if(mMarker.geometry !== undefined)
  2139. {
  2140. mIcon = document.querySelector('#'+mMarker.geometry.id);
  2141. }
  2142. else if(mMarker.tagName === "image")
  2143. {
  2144. mIcon = mMarker;
  2145. }
  2146. if((mIcon !== null) && (mIcon !== undefined))
  2147. {
  2148. mIcon.addEventListener("mouseover", uroMarkers.MouseOver2, true);
  2149. mIcon.addEventListener("mouseout", uroMarkers.MouseOut2, true);
  2150. }
  2151. }
  2152. }
  2153. }
  2154. }
  2155. }
  2156. },
  2157. AddMarkerEventHandler: function()
  2158. {
  2159. if
  2160. (
  2161. (uroMarkers.type === uroLayers.ID.UR) ||
  2162. (uroMarkers.type === uroLayers.ID.MP) ||
  2163. (uroMarkers.type === uroLayers.ID.PUR) ||
  2164. (uroMarkers.type === uroLayers.ID.PPUR) ||
  2165. (uroMarkers.type === uroLayers.ID.RPUR)
  2166. )
  2167. {
  2168. let mMarker = uroGetMarker(uroMarkers.type, uroMarkers.id);
  2169. if(mMarker !== null)
  2170. {
  2171. let mIcon = null;
  2172. if(mMarker.element !== undefined)
  2173. {
  2174. mIcon = mMarker.element;
  2175. }
  2176. else if(mMarker.geometry !== undefined)
  2177. {
  2178. mIcon = document.querySelector('#'+mMarker.geometry.id);
  2179. }
  2180. else if(mMarker.tagName === "image")
  2181. {
  2182. mIcon = mMarker;
  2183. }
  2184. if((mIcon !== null) && (mIcon !== undefined))
  2185. {
  2186. mIcon.addEventListener("mousedown", uroMarkers.MouseDown, false);
  2187. }
  2188. }
  2189. }
  2190. }
  2191. };
  2192. const uroLayers = // layer functions
  2193. {
  2194. // -----------------------------------------------------------------
  2195. // NOTE CAREFULLY!
  2196. // The contents of layers and ID MUST, MUST, MUST, remain
  2197. // in sync at all times...
  2198. layers :
  2199. [
  2200. {name: "update_requests", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: true},
  2201. {name: "mapProblems", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: true},
  2202. {name: "place_updates", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: true},
  2203. {name: "PARKING_PLACE_UPDATES", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: true},
  2204. {name: "RESIDENTIAL_PLACE_UPDATES", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: true},
  2205. {name: "closures", l: null, mf: null, isFeature: null, MO: null, regEvt: false, moChild: false, getMF: true},
  2206. {name: "nodes", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: false},
  2207. {name: "segments", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: false},
  2208. {name: "venues", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: false},
  2209. {name: "mapComments", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: false},
  2210. {name: "speed_cameras", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: false},
  2211. {name: "closure_nodes", l: null, mf: null, isFeature: null, MO: null, regEvt: false, moChild: false, getMF: true},
  2212. {name: "turn_closure", l: null, mf: null, isFeature: null, MO: null, regEvt: false, moChild: false, getMF: true},
  2213. {name: "segment_suggestions_markers", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: true},
  2214. {name: "edit_suggestions_markers", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: false, getMF: true},
  2215. {name: "permanent_hazard_camera_markers", l: null, mf: null, isFeature: null, MO: null, regEvt: true, moChild: true, getMF: true}
  2216. ],
  2217. ID :
  2218. {
  2219. UR: 0,
  2220. MP: 1,
  2221. PUR: 2,
  2222. PPUR: 3,
  2223. RPUR: 4,
  2224. RTC: 5,
  2225. node: 6,
  2226. seg: 7,
  2227. venue: 8,
  2228. MC: 9,
  2229. cam: 10,
  2230. RTCnode: 11,
  2231. TRTCnode: 12,
  2232. SegSug: 13,
  2233. EditSug: 14,
  2234. phCamera: 15
  2235. },
  2236. // -----------------------------------------------------------------
  2237.  
  2238. BlobMouseOut: function()
  2239. {
  2240. let blobType = this.attributes.uroBlobType;
  2241. if(blobType !== undefined)
  2242. {
  2243. let blobID = this.attributes.uroBlobID;
  2244. uroDBG.AddLog('hover off '+blobType+' ID '+blobID);
  2245. if(blobType == 'map_comment')
  2246. {
  2247. if(W.model.mapComments.objects[blobID] != undefined)
  2248. {
  2249. let geoID = W.model.mapComments.objects[blobID].attributes.geometry.id;
  2250. if(geoID.indexOf('Point') != -1)
  2251. {
  2252. // reapply visibility mods
  2253. let svgElm = document.getElementById(uroMCLayer.div.id+'_vroot');
  2254. for(let svgIdx = 0; svgIdx < svgElm.children.length; svgIdx++)
  2255. {
  2256. if(svgElm.children[svgIdx].id === geoID)
  2257. {
  2258. window.setTimeout(uroLayers.ReapplyPointMCVisibilityMods,10);
  2259. }
  2260. }
  2261. }
  2262. }
  2263. }
  2264. }
  2265. else
  2266. {
  2267. uroDBG.AddLog('hover off unknown blob...');
  2268. }
  2269. },
  2270. MCLayerChanged_changed: function()
  2271. {
  2272. uroLayers.MCLayerChanged();
  2273. },
  2274. MCLayerChanged_added: function()
  2275. {
  2276. uroLayers.MCLayerChanged();
  2277. },
  2278. MCLayerChanged_removed: function()
  2279. {
  2280. uroLayers.MCLayerChanged();
  2281. },
  2282. ReapplyPointMCVisibilityMods: function()
  2283. {
  2284. if(uroLayers.ApplyPointMCVisibilityMods() === false)
  2285. {
  2286. window.setTimeout(uroLayers.ReapplyPointMCVisibilityMods,100);
  2287. }
  2288. },
  2289. ApplyPointMCVisibilityMods: function()
  2290. {
  2291. let retval = true;
  2292. if(uroLayers.HasSelectedMCs() === true)
  2293. {
  2294. retval = false;
  2295. }
  2296. else
  2297. {
  2298. let svgElm = document.getElementById(uroMCLayer.div.id+'_vroot');
  2299. for(let svgIdx = 0; svgIdx < svgElm.children.length; svgIdx++)
  2300. {
  2301. let svgChild = svgElm.children[svgIdx];
  2302. if(svgChild.id.indexOf('Point') != -1)
  2303. {
  2304. if(uroUtils.GetCBChecked('_cbMCEnhancePointMCVisibility') === true)
  2305. {
  2306. if(svgChild.getAttribute('r') == 6)
  2307. {
  2308. svgChild.setAttribute('fill','#ffff00');
  2309. svgChild.setAttribute('fill-opacity',0.75);
  2310. svgChild.setAttribute('r',12);
  2311. svgChild.setAttribute('touchedByURO',true);
  2312. }
  2313. else if((svgChild.getAttribute('touchedByURO') === "true")&&(svgChild.getAttribute('fill') === '#ffff00'))
  2314. {
  2315. // do nothing...
  2316. }
  2317. else
  2318. {
  2319. retval = false;
  2320. break;
  2321. }
  2322. }
  2323. else
  2324. {
  2325. if((svgChild.getAttribute('touchedByURO') === "true")&&(svgChild.getAttribute('fill') === '#ffff00'))
  2326. {
  2327. svgChild.setAttribute('fill','#ffffff');
  2328. svgChild.setAttribute('fill-opacity',1);
  2329. svgChild.setAttribute('r',6);
  2330. svgChild.setAttribute('touchedByURO',false);
  2331. }
  2332. }
  2333. }
  2334. }
  2335. }
  2336. return retval;
  2337. },
  2338. HasSelectedMCs: function()
  2339. {
  2340. let retval = false;
  2341. for(let mcObj in W.model.mapComments.objects)
  2342. {
  2343. if(W.model.mapComments.objects[mcObj].isSelected() === true)
  2344. {
  2345. retval = true;
  2346. break;
  2347. }
  2348. }
  2349. return retval;
  2350. },
  2351. MCLayerChanged: function()
  2352. {
  2353. uroInit.WazeBits();
  2354. if(uroMCLayer != null)
  2355. {
  2356. if(uroLayers.HasSelectedMCs() === false)
  2357. {
  2358. uroDBG.AddLog('adding MC blob event handlers');
  2359. let mcModel = null;
  2360. for(let mObj=0; mObj<uroMCLayer.features.length; mObj++)
  2361. {
  2362. if(uroMCLayer.features[mObj]?.attributes?.wazeFeature?._wmeObject !== undefined)
  2363. {
  2364. mcModel = uroMCLayer.features[mObj].attributes.wazeFeature._wmeObject;
  2365. {
  2366. if(mcModel.selected !== true)
  2367. {
  2368. let mcBlobID = mcModel.attributes.geometry.id;
  2369. let mcID = mcModel.attributes.id;
  2370. let mcBlob = document.getElementById(mcBlobID);
  2371. if(mcBlob !== null)
  2372. {
  2373. mcBlob.addEventListener("mouseout", uroLayers.blobMouseOut, false);
  2374. mcBlob.attributes.uroBlobID = mcID;
  2375. mcBlob.attributes.uroBlobType = "map_comment";
  2376. uroDBG.AddLog('added handlers to MC '+mcID);
  2377. }
  2378. }
  2379. }
  2380. }
  2381. }
  2382. uroLayers.ApplyPointMCVisibilityMods();
  2383. }
  2384. else
  2385. {
  2386. uroDBG.AddLog('MC selected, handlers not added yet...');
  2387. }
  2388. uroFilterMapComments();
  2389. }
  2390. },
  2391. VenueLayerChanged: function()
  2392. {
  2393. uroDBG.AddLog('adding place blob event handlers');
  2394. for(let mObj=0; mObj<uroVenueLayer.features.length; mObj++)
  2395. {
  2396. // clicking on an area place now adds the polygon drag handles into the features[] array, so we need to test that
  2397. // the current array entry isn't referring to one of these handles before trying to access the attributes...
  2398. if(uroVenueLayer.features[mObj]?.attributes?.wazeFeature?._wmeObject !== undefined)
  2399. {
  2400. let mcBlobID = uroVenueLayer.features[mObj].attributes.wazeFeature._wmeObject.attributes.geometry.id;
  2401. let mcID = uroVenueLayer.features[mObj].attributes.wazeFeature._wmeObject.attributes.id;
  2402. let mcBlob = document.getElementById(mcBlobID);
  2403. if(mcBlob !== null)
  2404. {
  2405. mcBlob.addEventListener("mouseout", uroLayers.blobMouseOut, false);
  2406. mcBlob.attributes.uroBlobID = mcID;
  2407. mcBlob.attributes.uroBlobType = "place";
  2408. }
  2409. }
  2410. }
  2411. },
  2412. Observe_VenueLayer: function()
  2413. {
  2414. uroLayers.layers[uroLayers.ID.venue].MO.observe(uroVenueLayer.div,{childList: true, attributes : true, characterData : true, subtree: true});
  2415. },
  2416. Observe_URLayer: function()
  2417. {
  2418. // As URs are now displayed as SVG image elements rather than HTML elements, and as WME likes to re-render them seemingly
  2419. // at random after they've been initially displayed, we hang the mutation observer off of the vectorRoot element, as this
  2420. // is the parent SVG element for the markers, and the MO therefore seems to trigger reliably on each change to that level
  2421. // of the SVG, including these random re-renders. It's obvious if these re-renders aren't being captured correctly, as it
  2422. // causes the comment count markers to randomly show up behind UR markers instead of always being in front of them...
  2423. uroLayers.layers[uroLayers.ID.UR].MO.observe(uroLayers.layers[uroLayers.ID.UR].l.renderer.vectorRoot,{childList: true, attributes : true, characterData : true, subtree: true});
  2424. },
  2425. URLayerChanged: function()
  2426. {
  2427. uroDBG.AddLog('UR layer change detected');
  2428. uroLayers.layers[uroLayers.ID.UR].MO.disconnect();
  2429. uroFilterURs();
  2430. uroLayers.Observe_URLayer();
  2431. },
  2432. PURLayerChanged: function()
  2433. {
  2434. uroDBG.AddLog('PUR layer change detected');
  2435. uroLayers.layers[uroLayers.ID.PUR].MO.disconnect();
  2436. uroFilterProblems();
  2437. uroLayers.Observe_PURLayer();
  2438. },
  2439. Observe_PURLayer: function()
  2440. {
  2441. uroLayers.layers[uroLayers.ID.PUR].MO.observe(uroLayers.layers[uroLayers.ID.PUR].l.div,{childList: true, attributes : true, characterData : true, subtree: true});
  2442. },
  2443. PPURLayerChanged: function()
  2444. {
  2445. uroDBG.AddLog('PPUR layer change detected');
  2446. uroLayers.layers[uroLayers.ID.PPUR].MO.disconnect();
  2447. uroFilterProblems();
  2448. uroLayers.Observe_PPURLayer();
  2449. },
  2450. Observe_PPURLayer: function()
  2451. {
  2452. uroLayers.layers[uroLayers.ID.PPUR].MO.observe(uroLayers.layers[uroLayers.ID.PPUR].l.div,{childList: true, attributes : true, characterData : true, subtree: true});
  2453. },
  2454. RPURLayerChanged: function()
  2455. {
  2456. uroDBG.AddLog('RPUR layer change detected');
  2457. uroLayers.layers[uroLayers.ID.RPUR].MO.disconnect();
  2458. uroFilterProblems();
  2459. uroLayers.Observe_RPURLayer();
  2460. },
  2461. Observe_RPURLayer: function()
  2462. {
  2463. uroLayers.layers[uroLayers.ID.RPUR].MO.observe(uroLayers.layers[uroLayers.ID.RPUR].l.div,{childList: true, attributes : true, characterData : true, subtree: true});
  2464. },
  2465.  
  2466. RTCLayerChanged: function()
  2467. {
  2468. uroDBG.AddLog('reapplying closures filter');
  2469. uroLayers.layers[uroLayers.ID.RTC].MO.disconnect();
  2470. uroFilterRTCs();
  2471. uroLayers.Observe_RTCLayer();
  2472. },
  2473. Observe_RTCLayer: function()
  2474. {
  2475. uroLayers.layers[uroLayers.ID.RTC].MO.observe(uroLayers.layers[uroLayers.ID.RTC].l.div,{childList: true, attributes : true, characterData : true, subtree: true});
  2476. },
  2477. RunChangeHandlers: function()
  2478. {
  2479. uroLayers.URLayerChanged();
  2480. uroLayers.PURLayerChanged();
  2481. uroLayers.PPURLayerChanged();
  2482. uroLayers.RPURLayerChanged();
  2483. uroLayers.VenueLayerChanged();
  2484. uroLayers.RTCLayerChanged();
  2485.  
  2486. uroLayers.MCLayerChanged();
  2487. },
  2488. InitialiseMOs: function()
  2489. {
  2490. uroLayers.layers[uroLayers.ID.UR].MO = new MutationObserver(uroLayers.URLayerChanged);
  2491. uroLayers.layers[uroLayers.ID.PUR].MO = new MutationObserver(uroLayers.PURLayerChanged);
  2492. uroLayers.layers[uroLayers.ID.PPUR].MO = new MutationObserver(uroLayers.PPURLayerChanged);
  2493. uroLayers.layers[uroLayers.ID.RPUR].MO = new MutationObserver(uroLayers.RPURLayerChanged);
  2494. uroLayers.layers[uroLayers.ID.venue].MO = new MutationObserver(uroLayers.VenueLayerChanged);
  2495. uroLayers.layers[uroLayers.ID.RTC].MO = new MutationObserver(uroLayers.RTCLayerChanged);
  2496. uroLayers.Observe_URLayer();
  2497. uroLayers.Observe_PURLayer();
  2498. uroLayers.Observe_PPURLayer();
  2499. uroLayers.Observe_RPURLayer();
  2500. uroLayers.Observe_VenueLayer();
  2501. uroLayers.Observe_RTCLayer();
  2502. },
  2503. GetMarkersOrFeatures: function(layerID)
  2504. {
  2505. let retval = null;
  2506. let findit = uroLayers.layers[layerID].l.features;
  2507. if(findit !== undefined)
  2508. {
  2509. retval = findit;
  2510. uroLayers.layers[layerID].isFeature = true;
  2511. uroDBG.AddLog(uroLayers.layers[layerID].name + ' = features');
  2512. }
  2513. else
  2514. {
  2515. findit = uroLayers.layers[layerID].l.markers;
  2516. if(findit !== undefined)
  2517. {
  2518. retval = findit;
  2519. uroLayers.layers[layerID].isFeature = false;
  2520. uroDBG.AddLog(uroLayers.layers[layerID].name + ' = markers');
  2521. }
  2522. }
  2523. if(retval === null)
  2524. {
  2525. uroLayers.layers[layerID].isFeature = null;
  2526. uroDBG.AddLog(uroLayers.layers[layerID].name + ' = unknown :-/');
  2527. }
  2528. return retval;
  2529. },
  2530. Init: function()
  2531. {
  2532. for(let i = 0; i < uroLayers.layers.length; ++i)
  2533. {
  2534. uroLayers.layers[i].l = W.map.getLayerByUniqueName(uroLayers.layers[i].name);
  2535. if(uroLayers.layers[i].getMF === true)
  2536. {
  2537. uroLayers.layers[i].mf = uroLayers.GetMarkersOrFeatures(i);
  2538. }
  2539. }
  2540. }
  2541. };
  2542. const uroAlertBox = // alert box handling
  2543. {
  2544. stack: [],
  2545. tickAction: null,
  2546. crossAction: null,
  2547. inUse: false,
  2548. ABObj: function(headericon, title, content, hasCross, tickText, crossText, tickAction, crossAction)
  2549. {
  2550. this.headericon = headericon;
  2551. this.title = title;
  2552. this.content = content;
  2553. this.hasCross = hasCross;
  2554. this.tickText = tickText;
  2555. this.crossText = crossText;
  2556. this.tickAction = tickAction;
  2557. this.crossAction = crossAction;
  2558. },
  2559. Close: function()
  2560. {
  2561. document.getElementById('uroAlerts').childNodes[0].innerHTML = uroUtils.ModifyHTML('');
  2562. document.getElementById('uroAlerts').childNodes[1].innerHTML = uroUtils.ModifyHTML('');
  2563. document.getElementById('uroAlertTickBtnCaption').innerHTML = uroUtils.ModifyHTML('');
  2564. document.getElementById('uroAlertCrossBtnCaption').innerHTML = uroUtils.ModifyHTML('');
  2565. uroAlertBox.tickAction = null;
  2566. uroAlertBox.crossAction = null;
  2567. document.getElementById('uroAlerts').style.visibility = "hidden";
  2568. document.getElementById('uroAlertCrossBtn').style.visibility = "hidden";
  2569. uroAlertBox.inUse = false;
  2570. if(uroAlertBox.stack.length > 0)
  2571. {
  2572. uroAlertBox.BuildFromStack();
  2573. }
  2574. },
  2575. CloseWithTick: function()
  2576. {
  2577. if(typeof uroAlertBox.tickAction === 'function')
  2578. {
  2579. uroAlertBox.tickAction();
  2580. }
  2581. uroAlertBox.Close();
  2582. },
  2583. CloseWithCross: function()
  2584. {
  2585. if(typeof uroAlertBox.crossAction === 'function')
  2586. {
  2587. uroAlertBox.crossAction();
  2588. }
  2589. uroAlertBox.Close();
  2590. },
  2591. Show: function(headericon, title, content, hasCross, tickText, crossText, tickAction, crossAction)
  2592. {
  2593. uroAlertBox.stack.push(new uroAlertBox.ABObj(headericon, title, content, hasCross, tickText, crossText, tickAction, crossAction));
  2594. if(uroAlertBox.inUse === false)
  2595. {
  2596. uroAlertBox.BuildFromStack();
  2597. }
  2598. },
  2599. BuildFromStack: function()
  2600. {
  2601. uroAlertBox.inUse = true;
  2602. uroAlertBox.tickAction = null;
  2603. uroAlertBox.crossAction = null;
  2604. let titleContent = '<span style="font-size:14px;padding:2px;">';
  2605. titleContent += '<i class="fa '+uroAlertBox.stack[0].headericon+'"> </i>&nbsp;';
  2606. titleContent += uroAlertBox.stack[0].title;
  2607. titleContent += '</span>';
  2608. document.getElementById('uroAlerts').childNodes[0].innerHTML = uroUtils.ModifyHTML(titleContent);
  2609. document.getElementById('uroAlerts').childNodes[1].innerHTML = uroUtils.ModifyHTML(uroAlertBox.stack[0].content);
  2610. document.getElementById('uroAlertTickBtnCaption').innerHTML = uroUtils.ModifyHTML(uroAlertBox.stack[0].tickText);
  2611. if(uroAlertBox.stack[0].hasCross)
  2612. {
  2613. document.getElementById('uroAlertCrossBtnCaption').innerHTML = uroUtils.ModifyHTML(uroAlertBox.stack[0].crossText);
  2614. document.getElementById('uroAlertCrossBtn').style.visibility = "visible";
  2615. if(typeof uroAlertBox.stack[0].crossAction === "function")
  2616. {
  2617. uroAlertBox.crossAction = uroAlertBox.stack[0].crossAction;
  2618. }
  2619. }
  2620. else
  2621. {
  2622. document.getElementById('uroAlertCrossBtn').style.visibility = "hidden";
  2623. }
  2624. if(typeof uroAlertBox.stack[0].tickAction === "function")
  2625. {
  2626. uroAlertBox.tickAction = uroAlertBox.stack[0].tickAction;
  2627. }
  2628. document.getElementById('uroAlerts').style.visibility = "";
  2629. uroAlertBox.stack.shift();
  2630. }
  2631. };
  2632. const uroStartup = // startup messaging to users
  2633. {
  2634. ShowUpgradeNotes: function()
  2635. {
  2636. uroDBG.AddLog('let users know what\'s new in this release');
  2637.  
  2638. let releaseNotes = '';
  2639. releaseNotes += '<p>Thanks for installing URO+ '+uroRelease.version+' ('+uroRelease.date+')</p>';
  2640.  
  2641. let loop;
  2642. if(uroRelease.changes.length > 0)
  2643. {
  2644. releaseNotes += '<br>Changes since the last release:<br>';
  2645. releaseNotes += '<ul>';
  2646. for(loop=0; loop < uroRelease.changes.length; loop++)
  2647. {
  2648. releaseNotes += '<li>'+uroRelease.changes[loop];
  2649. }
  2650. releaseNotes += '</ul>';
  2651. }
  2652.  
  2653. uroAlertBox.Show('fa-info-circle', 'URO+ Release Notes', releaseNotes, false, "OK", "", null, null);
  2654. }
  2655. };
  2656. const uroConfig = // configuration handling
  2657. {
  2658. GatherSettings: function(container)
  2659. {
  2660. let options = '';
  2661. if(typeof(container) == 'string')
  2662. {
  2663. container = document.getElementById(container);
  2664. }
  2665. let urOptions = container.getElementsByTagName('input');
  2666. for (let optIdx=0;optIdx<urOptions.length;optIdx++)
  2667. {
  2668. // Don't save settings for any of the legacy input elements we've now hidden...
  2669. if(urOptions[optIdx].style.display != "none")
  2670. {
  2671. let id = urOptions[optIdx].id;
  2672. if((id.indexOf('_cb') === 0)||(id.indexOf('_text') === 0)||(id.indexOf('_input') === 0))
  2673. {
  2674. options += ':' + id;
  2675. if(urOptions[optIdx].type == 'checkbox') options += ',' + urOptions[optIdx].checked.toString();
  2676. else if((urOptions[optIdx].type == 'text')||(urOptions[optIdx].type == 'number')) options += ',' + urOptions[optIdx].value.toString();
  2677. }
  2678. }
  2679. }
  2680. return options;
  2681. },
  2682. GatherCamWatchList: function()
  2683. {
  2684. let liststr = '';
  2685. for(let loop=0;loop<uroOWL.CamWatchObjects.length;loop++)
  2686. {
  2687. let camObj = uroOWL.CamWatchObjects[loop];
  2688. if((camObj.fid != null) && (camObj.persistent === true))
  2689. {
  2690. if(loop > 0) liststr += ':';
  2691.  
  2692. liststr += camObj.fid+',';
  2693. liststr += camObj.watch.lon+',';
  2694. liststr += camObj.watch.lat+',';
  2695. liststr += camObj.watch.type+',';
  2696. liststr += camObj.watch.azymuth+',';
  2697. liststr += camObj.watch.speed+',';
  2698. liststr += camObj.groupID+',';
  2699. liststr += camObj.server;
  2700. }
  2701. }
  2702. return liststr;
  2703. },
  2704. GatherCWLGroups: function()
  2705. {
  2706. let liststr = '';
  2707. for(let loop=0;loop<uroOWL.CWLGroups.length;loop++)
  2708. {
  2709. let groupObj = uroOWL.CWLGroups[loop];
  2710. if(groupObj.groupID != -1)
  2711. {
  2712. if(loop > 0) liststr += ':';
  2713.  
  2714. liststr += groupObj.groupID+',';
  2715. liststr += groupObj.groupName+',';
  2716. liststr += groupObj.groupCollapsed;
  2717. }
  2718. }
  2719. return liststr;
  2720. },
  2721. GatherPlacesGroups: function()
  2722. {
  2723. let liststr = '';
  2724. for(let loop=0;loop<uroPlacesGroupsCollapsed.length;loop++)
  2725. {
  2726. if(loop > 0) liststr += ':';
  2727. liststr += uroPlacesGroupsCollapsed[loop];
  2728. }
  2729. return liststr;
  2730. },
  2731. GatherAFNs: function()
  2732. {
  2733. let liststr = '';
  2734. for(let loop=0; loop < uroAFN.friendlyNames.length; loop++)
  2735. {
  2736. let fnObj = uroAFN.friendlyNames[loop];
  2737. if(loop > 0) liststr += ':';
  2738.  
  2739. liststr += fnObj.fName+',';
  2740. liststr += fnObj.area+',';
  2741. liststr += fnObj.server;
  2742. }
  2743. return liststr;
  2744. },
  2745. SaveSettings: function()
  2746. {
  2747. if((uroInhibitSave) || (uroMTEMode === true) || (uroSettingsApplied === false))
  2748. {
  2749. uroDBG.AddLog('save inhibited');
  2750. return;
  2751. }
  2752.  
  2753. if (localStorage)
  2754. {
  2755. try
  2756. {
  2757. for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
  2758. {
  2759. localStorage[uroTabs.CtrlTabs[i][uroTabs.FIELDS.STORAGE]] = uroConfig.GatherSettings(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY]);
  2760. }
  2761.  
  2762. localStorage.UROverviewCamWatchList = uroConfig.GatherCamWatchList();
  2763. localStorage.UROverviewCWLGroups = uroConfig.GatherCWLGroups();
  2764. localStorage.UROverviewFriendlyAreaNames = uroConfig.GatherAFNs();
  2765. localStorage.UROverviewPlacesGroups = uroConfig.GatherPlacesGroups();
  2766.  
  2767. localStorage.UROverviewMasterEnable = uroUtils.GetCBChecked('_cbMasterEnable');
  2768. localStorage.UROverviewCurrentVersion = uroRelease.version;
  2769.  
  2770. uroDBG.AddLog('save complete');
  2771. }
  2772. catch(err)
  2773. {
  2774. uroDBG.AddLog('exception thrown during save - probably script reload whilst in MTE mode...');
  2775. }
  2776. }
  2777. else
  2778. {
  2779. uroDBG.AddLog('no localStorage, save blocked');
  2780. }
  2781. },
  2782. ApplySettings: function(settings)
  2783. {
  2784. uroSettingsApplied = true;
  2785. if(settings != undefined)
  2786. {
  2787. if(document.querySelector('#_cbMasterEnable') === null)
  2788. {
  2789. uroSettingsApplied = false;
  2790. }
  2791. else
  2792. {
  2793. let options = settings.split(':');
  2794. for(let optIdx=0;optIdx<options.length;optIdx++)
  2795. {
  2796. let fields = options[optIdx].split(',');
  2797. if(fields[0].indexOf('_cb') === 0)
  2798. {
  2799. if(document.getElementById(fields[0]) !== null)
  2800. {
  2801. uroUtils.SetCBChecked(fields[0], (fields[1] == 'true'));
  2802. }
  2803. }
  2804. else if((fields[0].indexOf('_input') === 0)||(fields[0].indexOf('_text') === 0))
  2805. {
  2806. if(document.getElementById(fields[0]) !== null) document.getElementById(fields[0]).value = fields[1];
  2807. }
  2808. }
  2809. }
  2810. }
  2811. },
  2812. ApplyCamWatchList: function()
  2813. {
  2814. let objects = localStorage.UROverviewCamWatchList.split(':');
  2815. uroOWL.CamWatchObjects = [];
  2816. if(objects.length > 0)
  2817. {
  2818. for(let objIdx=0;objIdx<objects.length;objIdx++)
  2819. {
  2820. let fields = objects[objIdx].split(',');
  2821. if(fields.length == 9)
  2822. {
  2823. // CWL entries with 9 fields include the validated property which is now redundant, so we need to strip this property before adding
  2824. // the camera to the object collection. Whilst WME no longer displays unapproved cameras, it's preferable at this stage to leave
  2825. // any watched unapproved cameras in the object collection, just in case any of them were approved (and will therefore still be
  2826. // present in WME) inbetween the last time the user ran URO and now. For those unapproved cameras which were still unapproved when
  2827. // removed from WME, URO will then list them as deleted and the user can then perform a single manual tidy-up of their watchlist to
  2828. // remove them there as well.
  2829. uroOWL.CamWatchObjects.push(new uroOWL.CamWatchObj(true,fields[0],fields[1],fields[2],fields[3],fields[4],fields[5],fields[7],fields[8]));
  2830. }
  2831. else if(fields.length == 8)
  2832. {
  2833. uroOWL.CamWatchObjects.push(new uroOWL.CamWatchObj(true,fields[0],fields[1],fields[2],fields[3],fields[4],fields[5],fields[6],fields[7]));
  2834. }
  2835. }
  2836. }
  2837. },
  2838. ApplyCWLGroups: function()
  2839. {
  2840. let objects = localStorage.UROverviewCWLGroups.split(':');
  2841. uroOWL.CWLGroups = [];
  2842.  
  2843. if(objects.length === 0)
  2844. {
  2845. uroOWL.CWLGroups.push(new uroOWL.GroupObj(0,'No group',false));
  2846. }
  2847. else
  2848. {
  2849. for(let objIdx=0;objIdx<objects.length;objIdx++)
  2850. {
  2851. let fields = objects[objIdx].split(',');
  2852. if(fields.length < 2)
  2853. {
  2854. fields.push(false);
  2855. }
  2856. uroOWL.CWLGroups.push(new uroOWL.GroupObj(fields[0],fields[1],(fields[2] == 'true')));
  2857. }
  2858. }
  2859. },
  2860. TranslateLegacyMPTab: function()
  2861. {
  2862. let options = localStorage.UROverviewMPOptions.split(':');
  2863. for(let optIdx=0;optIdx<options.length;optIdx++)
  2864. {
  2865. let fields = options[optIdx].split(',');
  2866. if(fields[0].indexOf('_cb') === 0)
  2867. {
  2868. if(fields[0] == '_cbMPFilterParkingLotInputAsPoint') uroUtils.SetCBChecked('_cbMPFilter_T50', (fields[1] == 'true'));
  2869. if(fields[0] == '_cbMPMissingPLP_T70') uroUtils.SetCBChecked('_cbMPFilter_T70', (fields[1] == 'true'));
  2870. if(fields[0] == '_cbMPMissingPLP_T71') uroUtils.SetCBChecked('_cbMPFilter_T71', (fields[1] == 'true'));
  2871. if(fields[0] == '_cbMPFilterDrivingDirectionMismatch') uroUtils.SetCBChecked('_cbMPFilter_T101', (fields[1] == 'true'));
  2872. if(fields[0] == '_cbMPFilterMissingJunction') uroUtils.SetCBChecked('_cbMPFilter_T102', (fields[1] == 'true'));
  2873. if(fields[0] == '_cbMPFilterMissingRoad') uroUtils.SetCBChecked('_cbMPFilter_T103', (fields[1] == 'true'));
  2874. if(fields[0] == '_cbMPFilterCrossroadsJunctionMissing') uroUtils.SetCBChecked('_cbMPFilter_T104', (fields[1] == 'true'));
  2875. if(fields[0] == '_cbMPFilterRoadTypeMismatch') uroUtils.SetCBChecked('_cbMPFilter_T105', (fields[1] == 'true'));
  2876. if(fields[0] == '_cbMPFilterRestrictedTurn') uroUtils.SetCBChecked('_cbMPFilter_T106', (fields[1] == 'true'));
  2877. if(fields[0] == '_cbMPFilterTurnProblem') uroUtils.SetCBChecked('_cbMPFilter_T200', (fields[1] == 'true'));
  2878. if(fields[0] == '_cbMPFilterRoadClosureProblem') uroUtils.SetCBChecked('_cbMPFilter_T300', (fields[1] == 'true'));
  2879. }
  2880. }
  2881. },
  2882. TranslateLegacyZoom: function()
  2883. {
  2884. let tZoom = parseInt(document.getElementById("_inputFilterMinZoomLevel").value);
  2885. if(tZoom < 12)
  2886. {
  2887. tZoom += 12;
  2888. document.getElementById("_inputFilterMinZoomLevel").value = tZoom;
  2889. }
  2890. tZoom = parseInt(document.getElementById("_inputUnstackZoomLevel").value);
  2891. if(tZoom < 12)
  2892. {
  2893. tZoom += 12;
  2894. document.getElementById("_inputUnstackZoomLevel").value = tZoom;
  2895. }
  2896. },
  2897. LoadSettings: function()
  2898. {
  2899. let isNewInstall = true;
  2900. let isUpgradeInstall = true;
  2901.  
  2902. uroDBG.AddLog('loadSettings()');
  2903. for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
  2904. {
  2905. if (uroTabs.CtrlTabs[i][uroTabs.FIELDS.STORAGE] != null)
  2906. {
  2907. uroDBG.AddLog('recover '+uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABTITLE]+' tab settings');
  2908. uroConfig.ApplySettings(localStorage[uroTabs.CtrlTabs[i][uroTabs.FIELDS.STORAGE]]);
  2909. isNewInstall = false;
  2910. }
  2911. }
  2912. if (localStorage.UROverviewMPOptions != null)
  2913. {
  2914. uroConfig.TranslateLegacyMPTab();
  2915. }
  2916. if (localStorage.UROverviewMiscOptions != null)
  2917. {
  2918. uroConfig.TranslateLegacyZoom();
  2919. }
  2920.  
  2921. if(localStorage.UROverviewCWLGroups != null)
  2922. {
  2923. uroDBG.AddLog('recover CWL groups');
  2924. uroConfig.ApplyCWLGroups();
  2925. isNewInstall = false;
  2926. }
  2927. else
  2928. {
  2929. uroDBG.AddLog('set default CWL group');
  2930. uroOWL.CWLGroups.push(new uroOWL.GroupObj(0,'No group',false));
  2931. }
  2932.  
  2933. if(localStorage.UROverviewCamWatchList != null)
  2934. {
  2935. uroDBG.AddLog('recover camera watchlist');
  2936. uroConfig.ApplyCamWatchList();
  2937. uroOWL.GetCurrentCamWatchListObjects();
  2938. isNewInstall = false;
  2939. }
  2940. /*
  2941. if(localStorage.UROverviewSegWatchList != null)
  2942. {
  2943. uroDBG.AddLog('recover segment watchlist');
  2944. uroApplySegWatchList();
  2945. uroGetCurrentSegWatchListObjects();
  2946. isNewInstall = false;
  2947. }
  2948.  
  2949. if(localStorage.UROverviewPlaceWatchList != null)
  2950. {
  2951. uroDBG.AddLog('recover places watchlist');
  2952. uroApplyPlaceWatchList();
  2953. //uroGetCurrentPlaceWatchListObjects();
  2954. isNewInstall = false;
  2955. }
  2956.  
  2957. if(localStorage.UROverviewPlacesGroups != null)
  2958. {
  2959. uroDBG.AddLog('recover places groups');
  2960. uroApplyPlacesGroups();
  2961. isNewInstall = false;
  2962. }
  2963. */
  2964. if(localStorage.UROverviewCurrentVersion != null)
  2965. {
  2966. uroDBG.AddLog('comparing install versions');
  2967. if(localStorage.UROverviewCurrentVersion == uroRelease.version)
  2968. {
  2969. isUpgradeInstall = false;
  2970. }
  2971. }
  2972.  
  2973. if(localStorage.UROverviewFriendlyAreaNames != null)
  2974. {
  2975. uroDBG.AddLog('recover friendly area names');
  2976. uroAFN.ApplyNames();
  2977. isNewInstall = false;
  2978. }
  2979.  
  2980. if(localStorage.UROverviewMasterEnable != null)
  2981. {
  2982. uroDBG.AddLog('recover master enable state');
  2983. document.getElementById('_cbMasterEnable').checked = (localStorage.UROverviewMasterEnable == "true");
  2984. uroDBG.AddLog('enable checkbox state set...');
  2985. }
  2986.  
  2987. if((isNewInstall)||(isUpgradeInstall))
  2988. {
  2989. uroStartup.ShowUpgradeNotes();
  2990. }
  2991.  
  2992. uroInhibitSave = false;
  2993. },
  2994. DefaultSettings: function()
  2995. {
  2996. uroAlertBox.Show("fa-warning", "URO+ Warning", "Resetting URO+ settings <b>cannot</b> be undone.<br>Are you <i>sure</i> you want to do this?", true, "Reset settings", "Keep settings", uroConfig.DefaultSettingsAction, null);
  2997. },
  2998. DefaultSettingsAction: function()
  2999. {
  3000. let defaultSettings = '';
  3001.  
  3002. defaultSettings += '[UROverviewUROptions][len=1849]:_cbURFilterOutsideArea,false:_cbURFilterInsideManagedAreas,false:_cbURExcludeUserArea,false:_cbNoFilterForURInURL,false:_cbURFilterDupes,false:_cbFilterWazeAuto,false:_cbFilterIncorrectTurn,false:_cbFilterIncorrectAddress,false:_cbFilterIncorrectRoute,false:_cbFilterMissingRoundabout,false:_cbFilterGeneralError,false:_cbFilterTurnNotAllowed,false:_cbFilterIncorrectJunction,false:_cbFilterMissingBridgeOverpass,false:_cbFilterWrongDrivingDirection,false:_cbFilterMissingExit,false:_cbFilterMissingRoad,false:_cbFilterBlockedRoad,false:_cbFilterMissingLandmark,false:_cbFilterSpeedLimits,false:_cbFilterUndefined,false:_cbFilterRoadworks,false:_cbFilterConstruction,false:_cbFilterClosure,false:_cbFilterEvent,false:_cbFilterNote,false:_cbFilterBOG,false:_cbFilterDifficult,false:_cbFilterWSLM,false:_cbInvertURFilter,false:_cbFilterOpenUR,false:_cbFilterClosedUR,false:_cbFilterSolved,false:_cbFilterUnidentified,false:_cbEnableMinAgeFilter,false:_inputFilterMinDays,60:_cbEnableMaxAgeFilter,false:_inputFilterMaxDays,62:_cbHideMyFollowed,false:_cbHideMyUnfollowed,false:_cbURDescriptionMustBePresent,false:_cbURDescriptionMustBeAbsent,false:_cbEnableKeywordMustBePresent,false:_textKeywordPresent,:_cbEnableKeywordMustBeAbsent,false:_textKeywordAbsent,:_cbCaseInsensitive,false:_cbHideMyComments,false:_cbHideAnyComments,false:_cbHideIfLastCommenter,false:_cbHideIfNotLastCommenter,false:_cbHideIfReporterLastCommenter,false:_cbHideIfReporterNotLastCommenter,false:_cbEnableMinCommentsFilter,false:_inputFilterMinComments,1:_cbEnableMaxCommentsFilter,false:_inputFilterMaxComments,0:_cbEnableCommentAgeFilter2,false:_inputFilterCommentDays2,:_cbEnableCommentAgeFilter,false:_inputFilterCommentDays,1:_cbIgnoreOtherEditorComments,false:_cbURUserIDFilter,false:_cbURResolverIDFilter,false:_cbInvertURStateFilter,false:_cbNoFilterForTaggedURs,false[END]';
  3003. defaultSettings += '[UROverviewMiscOptions][len=1157]:_cbHideSegmentsWhenRoadsHidden,false:_cbKillInertialPanning,false:_cbCommentCount,false:_cbAutoApplyClonedClosure,false:_cbAutoScrollClosureList,false:_inputFilterMinZoomLevel,22:_inputUnstackSensitivity,30:_inputUnstackZoomLevel,22:_inputPopupEntryTimeout,10:_inputPopupExitTimeout,2:_inputPopupAutoHideTimeout,0:_cbInhibitURClusters,false:_cbInhibitMPClusters,false:_cbInhibitPUClusters,false:_cbInhibitURPopup,false:_cbInhibitMPPopup,false:_cbInhibitCamPopup,false:_cbInhibitSegPopup,false:_cbInhibitSegGenericPopup,false:_cbInhibitLandmarkPopup,false:_cbInhibitPUPopup,false:_cbInhibitMapCommentPopup,false:_cbInhibitNodesPopup,false:_cbDateFmtDDMMYY,true:_cbDateFmtMMDDYY,false:_cbDateFmtYYMMDD,false:_cbTimeFmt24H,true:_cbTimeFmt12H,false:_cbWhiteBackground,false:_inputCustomBackgroundRed,30:_inputCustomBackgroundGreen,30:_inputCustomBackgroundBlue,30:_cbInhibitNURButton,false:_cbInhibitNMPButton,false:_cbInhibitNPURButton,false:_cbInhibitURCentering,false:_cbInhibitMPCentering,false:_cbInhibitPURCentering,false:_cbInhibitPPURCentering,false:_cbInhibitRPURCentering,false:_cbHideAMLayer,false:_cbMoveAMList,false:_cbDisablePlacesFiltering,false[END]';
  3004. defaultSettings += '[UROverviewPlacesOptions][len=6292]:_cbFilterUneditablePlaceUpdates,false:_cbPURFilterInsideManagedAreas,false:_cbPURExcludeUserArea,false:_cbFilterLockRankedPlaceUpdates,false:_cbFilterNewPlacePUR,false:_cbFilterUpdatedDetailsPUR,false:_cbPURFilterCFPhone,false:_cbPURFilterCFName,false:_cbPURFilterCFEntryExitPoints,false:_cbPURFilterCFOpeningHours,false:_cbPURFilterCFAliases,false:_cbPURFilterCFServices,false:_cbPURFilterCFGeometry,false:_cbPURFilterCFHouseNumber,false:_cbPURFilterCFCategories,false:_cbPURFilterCFDescription,false:_cbFilterNewPhotoPUR,false:_cbFilterFlaggedPUR,false:_cbInvertPURFilters,false:_cbEnablePURMinAgeFilter,false:_inputPURFilterMinDays,3:_cbEnablePURMaxAgeFilter,false:_inputPURFilterMaxDays,4:_cbPlaceFilterEditedLessThan,false:_inputFilterPlaceEditMinDays,:_cbPlaceFilterEditedMoreThan,false:_inputFilterPlaceEditMaxDays,:_cbHidePlacesL0,false:_cbHidePlacesL1,false:_cbHidePlacesL2,false:_cbHidePlacesL3,false:_cbHidePlacesL4,false:_cbHidePlacesL5,false:_cbHidePlacesStaff,false:_cbHidePlacesAdLocked,false:_cbHideAreaPlaces,false:_cbHidePointPlaces,false:_cbHidePhotoPlaces,false:_cbHideNoPhotoPlaces,false:_cbHideLinkedPlaces,false:_cbHideNoLinkedPlaces,false:_cbHideDescribedPlaces,false:_cbHideNonDescribedPlaces,false:_cbHideKeywordPlaces,false:_cbHideNoKeywordPlaces,false:_textKeywordPlace,:_cbShowOnlyPlacesCreatedBy,false:_cbShowOnlyPlacesEditedBy,false:_textPlacesEditor,theMadcabbie:_cbHideOnlyPlacesCreatedBy,false:_cbHideOnlyPlacesEditedBy,false:_textHidePlacesEditor,theMadcabbie:_cbLeavePURGeos,false:_cbHidePURsForFilteredPlaces,false:_cbPlacesFilter-CAR_SERVICES,false:_cbPlacesFilter-CAR_WASH,false:_cbPlacesFilter-CHARGING_STATION,false:_cbPlacesFilter-GARAGE_AUTOMOTIVE_SHOP,false:_cbPlacesFilter-GAS_STATION,false:_cbPlacesFilter-CRISIS_LOCATIONS,false:_cbPlacesFilter-DONATION_CENTERS,false:_cbPlacesFilter-OTHER_CRISIS_LOCATIONS,false:_cbPlacesFilter-SHELTER_LOCATIONS,false:_cbPlacesFilter-CULTURE_AND_ENTERTAINEMENT,false:_cbPlacesFilter-ART_GALLERY,false:_cbPlacesFilter-CASINO,false:_cbPlacesFilter-CLUB,false:_cbPlacesFilter-TOURIST_ATTRACTION_HISTORIC_SITE,false:_cbPlacesFilter-MOVIE_THEATER,false:_cbPlacesFilter-MUSEUM,false:_cbPlacesFilter-MUSIC_VENUE,false:_cbPlacesFilter-PERFORMING_ARTS_VENUE,false:_cbPlacesFilter-GAME_CLUB,false:_cbPlacesFilter-STADIUM_ARENA,false:_cbPlacesFilter-THEME_PARK,false:_cbPlacesFilter-ZOO_AQUARIUM,false:_cbPlacesFilter-RACING_TRACK,false:_cbPlacesFilter-THEATER,false:_cbPlacesFilter-FOOD_AND_DRINK,false:_cbPlacesFilter-RESTAURANT,false:_cbPlacesFilter-BAKERY,false:_cbPlacesFilter-DESSERT,false:_cbPlacesFilter-CAFE,false:_cbPlacesFilter-FAST_FOOD,false:_cbPlacesFilter-FOOD_COURT,false:_cbPlacesFilter-BAR,false:_cbPlacesFilter-ICE_CREAM,false:_cbPlacesFilter-LODGING,false:_cbPlacesFilter-HOTEL,false:_cbPlacesFilter-HOSTEL,false:_cbPlacesFilter-CAMPING_TRAILER_PARK,false:_cbPlacesFilter-COTTAGE_CABIN,false:_cbPlacesFilter-BED_AND_BREAKFAST,false:_cbPlacesFilter-NATURAL_FEATURES,false:_cbPlacesFilter-ISLAND,false:_cbPlacesFilter-SEA_LAKE_POOL,false:_cbPlacesFilter-RIVER_STREAM,false:_cbPlacesFilter-FOREST_GROVE,false:_cbPlacesFilter-FARM,false:_cbPlacesFilter-CANAL,false:_cbPlacesFilter-SWAMP_MARSH,false:_cbPlacesFilter-DAM,false:_cbPlacesFilter-OTHER,false:_cbPlacesFilter-CONSTRUCTION_SITE,false:_cbPlacesFilter-OUTDOORS,false:_cbPlacesFilter-PARK,false:_cbPlacesFilter-PLAYGROUND,false:_cbPlacesFilter-BEACH,false:_cbPlacesFilter-SPORTS_COURT,false:_cbPlacesFilter-GOLF_COURSE,false:_cbPlacesFilter-PLAZA,false:_cbPlacesFilter-PROMENADE,false:_cbPlacesFilter-POOL,false:_cbPlacesFilter-SCENIC_LOOKOUT_VIEWPOINT,false:_cbPlacesFilter-SKI_AREA,false:_cbPlacesFilter-PARKING_LOT,false:_cbPlacesFilter-PROFESSIONAL_AND_PUBLIC,false:_cbPlacesFilter-COLLEGE_UNIVERSITY,false:_cbPlacesFilter-SCHOOL,false:_cbPlacesFilter-CONVENTIONS_EVENT_CENTER,false:_cbPlacesFilter-GOVERNMENT,false:_cbPlacesFilter-LIBRARY,false:_cbPlacesFilter-CITY_HALL,false:_cbPlacesFilter-ORGANIZATION_OR_ASSOCIATION,false:_cbPlacesFilter-PRISON_CORRECTIONAL_FACILITY,false:_cbPlacesFilter-COURTHOUSE,false:_cbPlacesFilter-CEMETERY,false:_cbPlacesFilter-FIRE_DEPARTMENT,false:_cbPlacesFilter-POLICE_STATION,false:_cbPlacesFilter-MILITARY,false:_cbPlacesFilter-HOSPITAL_URGENT_CARE,false:_cbPlacesFilter-DOCTOR_CLINIC,false:_cbPlacesFilter-OFFICES,false:_cbPlacesFilter-POST_OFFICE,false:_cbPlacesFilter-RELIGIOUS_CENTER,false:_cbPlacesFilter-KINDERGARDEN,false:_cbPlacesFilter-FACTORY_INDUSTRIAL,false:_cbPlacesFilter-EMBASSY_CONSULATE,false:_cbPlacesFilter-INFORMATION_POINT,false:_cbPlacesFilter-EMERGENCY_SHELTER,false:_cbPlacesFilter-TRASH_AND_RECYCLING_FACILITIES,false:_cbPlacesFilter-SHOPPING_AND_SERVICES,false:_cbPlacesFilter-ARTS_AND_CRAFTS,false:_cbPlacesFilter-BANK_FINANCIAL,false:_cbPlacesFilter-SPORTING_GOODS,false:_cbPlacesFilter-BOOKSTORE,false:_cbPlacesFilter-PHOTOGRAPHY,false:_cbPlacesFilter-CAR_DEALERSHIP,false:_cbPlacesFilter-FASHION_AND_CLOTHING,false:_cbPlacesFilter-CONVENIENCE_STORE,false:_cbPlacesFilter-PERSONAL_CARE,false:_cbPlacesFilter-DEPARTMENT_STORE,false:_cbPlacesFilter-PHARMACY,false:_cbPlacesFilter-ELECTRONICS,false:_cbPlacesFilter-FLOWERS,false:_cbPlacesFilter-FURNITURE_HOME_STORE,false:_cbPlacesFilter-GIFTS,false:_cbPlacesFilter-GYM_FITNESS,false:_cbPlacesFilter-SWIMMING_POOL,false:_cbPlacesFilter-HARDWARE_STORE,false:_cbPlacesFilter-MARKET,false:_cbPlacesFilter-SUPERMARKET_GROCERY,false:_cbPlacesFilter-JEWELRY,false:_cbPlacesFilter-LAUNDRY_DRY_CLEAN,false:_cbPlacesFilter-SHOPPING_CENTER,false:_cbPlacesFilter-MUSIC_STORE,false:_cbPlacesFilter-PET_STORE_VETERINARIAN_SERVICES,false:_cbPlacesFilter-TOY_STORE,false:_cbPlacesFilter-TRAVEL_AGENCY,false:_cbPlacesFilter-ATM,false:_cbPlacesFilter-CURRENCY_EXCHANGE,false:_cbPlacesFilter-CAR_RENTAL,false:_cbPlacesFilter-TELECOM,false:_cbPlacesFilter-TRANSPORTATION,false:_cbPlacesFilter-AIRPORT,false:_cbPlacesFilter-BUS_STATION,false:_cbPlacesFilter-FERRY_PIER,false:_cbPlacesFilter-SEAPORT_MARINA_HARBOR,false:_cbPlacesFilter-SUBWAY_STATION,false:_cbPlacesFilter-TRAIN_STATION,false:_cbPlacesFilter-BRIDGE,false:_cbPlacesFilter-TUNNEL,false:_cbPlacesFilter-TAXI_STATION,false:_cbPlacesFilter-JUNCTION_INTERCHANGE,false:_cbPlacesFilter-REST_AREAS,false:_cbPlacesFilter-CARPOOL_SPOT,false:_cbFilterPrivatePlaces,false:_cbInvertPlacesFilter,false[END]';
  3005. defaultSettings += '[UROverviewPlacesGroups][len=71]false:false:false:false:false:false:false:false:false:false:false:false[END]';
  3006. defaultSettings += '[UROverviewMPOptions][len=1446]:_cbMPFilterOutsideArea,false:_cbMPFilter_T1,false:_cbMPFilter_T2,false:_cbMPFilter_T3,false:_cbMPFilter_T5,false:_cbMPFilter_T6,false:_cbMPFilter_T7,false:_cbMPFilter_T8,false:_cbMPFilter_T10,false:_cbMPFilter_T11,false:_cbMPFilter_T12,false:_cbMPFilter_T13,false:_cbMPFilter_T14,false:_cbMPFilter_T15,false:_cbMPFilter_T16,false:_cbMPFilter_T17,false:_cbMPFilter_T19,false:_cbMPFilter_T20,false:_cbMPFilter_T21,false:_cbMPFilter_T22,false:_cbMPFilter_T23,false:_cbMPFilter_T50,false:_cbMPFilter_T51,false:_cbMPFilter_T52,false:_cbMPFilter_T53,false:_cbMPFilter_T70,false:_cbMPFilter_T71,false:_cbMPFilter_T101,false:_cbMPFilter_T102,false:_cbMPFilter_T103,false:_cbMPFilter_T104,false:_cbMPFilter_T105,false:_cbMPFilter_T106,false:_cbMPFilter_T200,false:_cbMPFilter_T300,false:_cbMPFilterUnknownProblem,false:_cbFilterElgin,false:_cbFilterTrafficCast,false:_cbFilterTrafficMaster,false:_cbFilterCaltrans,false:_cbFilterTFL,false:_cbMPFilterReopenedProblem,false:_cbInvertMPFilter,false:_cbMPFilterClosed,false:_cbMPFilterSolved,false:_cbMPFilterUnidentified,false:_cbMPClosedUserIDFilter,false:_cbMPNotClosedUserIDFilter,false:_cbMPFilterLowSeverity,false:_cbMPFilterMediumSeverity,false:_cbMPFilterHighSeverity,false:_cbMPFilterStartDate,false:_inputMPFilterStartDay,:_inputMPFilterStartMonth,:_inputMPFilterStartYear,:_cbMPFilterEndDate,false:_inputMPFilterEndDay,:_inputMPFilterEndMonth,:_inputMPFilterEndYear,:_cbMPFilterEndDatePassed,false[END]';
  3007. defaultSettings += '[UROverviewMasterEnable][len=4]true[END]';
  3008. defaultSettings += '[UROverviewCWLGroups][len=16]0,No group,false[END]';
  3009. defaultSettings += '[UROverviewMCOptions][len=828]:_cbMCFilterRoadworks,false:_cbMCFilterConstruction,false:_cbMCFilterClosure,false:_cbMCFilterEvent,false:_cbMCFilterNote,false:_cbMCFilterBOG,false:_cbMCFilterDifficult,false:_cbMCFilterWSLM,false:_cbInvertMCFilter,false:_cbMCHideMyFollowed,false:_cbMCHideMyUnfollowed,false:_cbMCDescriptionMustBePresent,false:_cbMCDescriptionMustBeAbsent,false:_cbMCCommentsMustBePresent,false:_cbMCCommentsMustBeAbsent,false:_cbMCExpiryMustBePresent,false:_cbMCExpiryMustBeAbsent,false:_cbMCEnableKeywordMustBePresent,false:_textMCKeywordPresent,:_cbMCEnableKeywordMustBeAbsent,false:_textMCKeywordAbsent,:_cbMCCaseInsensitive,false:_cbMCCreatorIDFilter,false:_cbHideWRCMCs,false:_cbHideMCRank0,false:_cbHideMCRank1,false:_cbHideMCRank2,false:_cbHideMCRank3,false:_cbHideMCRank4,false:_cbHideMCRank5,false:_cbMCEnhancePointMCVisibility,false[END]';
  3010. defaultSettings += '[UROverviewRTCOptions][len=710]:_cbHideExpiredEditorRTCs,false:_cbHideEditorRTCs,false:_cbHideFutureEditorRTCs,false:_cbHideExpiredWazeFeedRTCs,false:_cbHideWazeFeedRTCs,false:_cbHideFutureWazeFeedRTCs,false:_cbHideExpiredWazeRTCs,false:_cbHideWazeRTCs,false:_cbHideFutureWazeRTCs,false:_cbHideExpiredSidepanelRTCs,false:_cbHideSidepanelRTCs,false:_cbHideFutureSidepanelRTCs,false:_cbShowMTERTCs,false:_cbHideMTERTCs,false:_cbEnableRTCDurationFilterLessThan,false:_inputFilterRTCDurationLessThan,:_cbEnableRTCDurationFilterMoreThan,false:_inputFilterRTCDurationMoreThan,:_cbRTCFilterShowForTS,false:_cbRTCFilterHideForTS,false:_inputRTCFilterDay,15:_inputRTCFilterMonth,6:_inputRTCFilterYear,2024:_inputRTCFilterHour,11:_inputRTCFilterMin,15[END]';
  3011. defaultSettings += '[UROverviewCameraOptions][len=908]:_cbShowWorldCams,true:_cbShowUSACams,true:_cbShowNonWorldCams,true:_cbShowOnlyCamsCreatedBy,false:_cbShowOnlyCamsEditedBy,false:_textCameraEditor,:_cbShowOnlyMyCams,false:_cbShowSpeedCams,true:_cbShowIfSpeedSet,true:_cbShowIfNoSpeedSet,true:_cbShowIfInvalidSpeedSet,true:_cbShowRedLightCams,true:_cbShowRLCIfZeroSpeedSet,true:_cbShowRLCIfNonZeroSpeedSet,true:_cbShowRLCIfNoSpeedSet,true:_cbShowDummyCams,true:_cbHideCreatedByMe,false:_cbHideCreatedByRank0,false:_cbHideCreatedByRank1,false:_cbHideCreatedByRank2,false:_cbHideCreatedByRank3,false:_cbHideCreatedByRank4,false:_cbHideCreatedByRank5,false:_cbHideUpdatedByMe,false:_cbHideUpdatedByRank0,false:_cbHideUpdatedByRank1,false:_cbHideUpdatedByRank2,false:_cbHideUpdatedByRank3,false:_cbHideUpdatedByRank4,false:_cbHideUpdatedByRank5,false:_cbHideManualLockedCams,false:_cbHideCWLCams,false:_cbInvertCamFilters,false:_cbHighlightInsteadOfHideCams,false[END]';
  3012. defaultSettings += '[UROverviewRAOptions][len=178]:_cbShowSpecificRA,false:_cbRAEditorIDFilter,false:_cbEnableRAAgeFilterLessThan,false:_inputFilterRAAgeLessThan,39:_cbEnableRAAgeFilterMoreThan,false:_inputFilterRAAgeMoreThan,38[END]';
  3013. defaultSettings += '[UROverviewCurrentVersion][len=3]4.5[END]';
  3014. defaultSettings += '[UROverviewCamWatchList][len=0][END]';
  3015. defaultSettings += '[UROverviewFriendlyAreaNames][len=0][END]';
  3016. defaultSettings += '[UROverviewPlaceWatchList][len=0][END]';
  3017. defaultSettings += '[UROverviewSegWatchList][len=0][END]';
  3018.  
  3019. document.getElementById('_txtSettings').value = defaultSettings;
  3020. uroConfig.TextToSettings();
  3021. document.getElementById('_txtSettings').value = '';
  3022. },
  3023. SettingsToText: function()
  3024. {
  3025. let txtSettings = '';
  3026.  
  3027. uroConfig.SaveSettings();
  3028.  
  3029. for(let lsEntry in localStorage)
  3030. {
  3031. if(lsEntry.indexOf('UROverview') === 0)
  3032. {
  3033. txtSettings += '['+lsEntry+'][len=' + localStorage[lsEntry].length + ']' + localStorage[lsEntry] + '[END]\n';
  3034. }
  3035. }
  3036.  
  3037. document.getElementById('_txtSettings').value = txtSettings;
  3038. document.getElementById('_txtSettings').focus();
  3039. document.getElementById('_txtSettings').select();
  3040. },
  3041. TextToSettings: function()
  3042. {
  3043. let txtSettings = '';
  3044. txtSettings = uroUtils.GetElmValue('_txtSettings');
  3045. if(txtSettings.indexOf('[END]') == -1) return;
  3046.  
  3047. let subText = txtSettings.split('[END]');
  3048. for(let i=0;i<subText.length;i++)
  3049. {
  3050. let aPos = subText[i].indexOf('[');
  3051. let bPos = subText[i].indexOf(']');
  3052. if((aPos != -1) && (bPos != -1))
  3053. {
  3054. let settingID = subText[i].substr(aPos+1,bPos-1-aPos);
  3055. subText[i] = subText[i].substr(bPos+1);
  3056. bPos = subText[i].indexOf(']');
  3057. if(bPos != -1)
  3058. {
  3059. let settingLength = subText[i].substr(5,bPos-5);
  3060. subText[i] = subText[i].substr(bPos+1);
  3061. if(subText[i].length == settingLength)
  3062. {
  3063. localStorage[settingID] = subText[i];
  3064. }
  3065. }
  3066. }
  3067. }
  3068. uroConfig.LoadSettings();
  3069. },
  3070. ClearSettingsText: function()
  3071. {
  3072. document.getElementById('_txtSettings').value = '';
  3073. }
  3074. };
  3075. const uroRTCClone = // RTC cloning
  3076. {
  3077. ConfirmDelete : true,
  3078. ToDelete : 0,
  3079. Reason : null,
  3080. Event : null,
  3081. Direction : null,
  3082. StartDate : null,
  3083. StartTime : null,
  3084. EndDate : null,
  3085. EndTime : null,
  3086. IgnoreTraffic : null,
  3087. ClosedNodes : null,
  3088. PendingClone : -1,
  3089. PendingCloneIncrement : 0,
  3090.  
  3091. Complete: function()
  3092. {
  3093. let loop;
  3094. if(document.getElementsByClassName('edit-closure').length === 0)
  3095. {
  3096. window.setTimeout(uroRTCClone.Complete,100);
  3097. return;
  3098. }
  3099.  
  3100. if(uroFixMTEDropDown(document.getElementById('closure_eventId')) == false)
  3101. {
  3102. window.setTimeout(uroRTCClone.Complete,100);
  3103. return;
  3104. }
  3105.  
  3106. // need to generate a change event on each of the form fields, because WME appears to be silently populating some hidden
  3107. // closure object with the details as they're entered manually, and if we just set the form values without then forcing
  3108. // the change event as well then WME will end up using its default values instead of the ones we've so lovingly copied...
  3109. let form = $('#edit-panel .closures .edit-closure form');
  3110.  
  3111. if(uroRTCClone.Reason !== null)
  3112. {
  3113. let fObj = form.find('wz-text-input#closure_reason');
  3114. fObj.val(uroRTCClone.Reason);
  3115. fObj.change();
  3116. }
  3117. if(uroRTCClone.Direction !== null)
  3118. {
  3119. let fObj = form.find('wz-select#closure_direction');
  3120. fObj[0].value = uroRTCClone.Direction;
  3121. fObj.change();
  3122. }
  3123. if(uroRTCClone.StartDate !== null)
  3124. {
  3125. let fObj = form.find('input#closure_startDate');
  3126. fObj.val(uroRTCClone.StartDate);
  3127. fObj.change();
  3128. }
  3129. if(uroRTCClone.StartTime !== null)
  3130. {
  3131. let fObj = form.find('div.form-group.start-date-form-group input.time-picker-input');
  3132. fObj.focus();
  3133. fObj.val(uroRTCClone.StartTime);
  3134. fObj.change();
  3135. }
  3136.  
  3137. if(uroRTCClone.IgnoreTraffic !== null)
  3138. {
  3139. let fObj = form.find('wz-checkbox#closure_permanent');
  3140. fObj.val(uroRTCClone.IgnoreTraffic);
  3141. fObj.change();
  3142. }
  3143. if(uroRTCClone.EndTime !== null)
  3144. {
  3145. let fObj = form.find('div.form-group.end-date-form-group input.time-picker-input');
  3146. fObj.focus();
  3147. fObj.val(uroRTCClone.EndTime);
  3148. fObj.change();
  3149. }
  3150.  
  3151. // the current version of WME wipes any existing end date as soon as the end time is altered, so we now need
  3152. // to set the date after the time instead of before as in earlier versions of this function...
  3153. if(uroRTCClone.EndDate !== null)
  3154. {
  3155. let fObj = form.find('input#closure_endDate');
  3156. fObj.val(uroRTCClone.EndDate);
  3157. fObj.change();
  3158. }
  3159.  
  3160. // the old method of setting the MTE just by changing the value attribute on closure_eventId no longer
  3161. // seems to work as expected (it runs OK from the dev console, but not within the scope of the userscript),
  3162. // so just as we do for setting the event to None, we set the event to the desired value here by finding
  3163. // the appropriate menu entry and clicking on it...
  3164. let cEvents = document.getElementById('closure_eventId').getElementsByTagName('wz-option');
  3165. for(let i of cEvents)
  3166. {
  3167. if(i.value == uroRTCClone.Event)
  3168. {
  3169. i.click();
  3170. break;
  3171. }
  3172. }
  3173.  
  3174. let nNodes = uroRTCClone.ClosedNodes.length;
  3175. if(nNodes > 0)
  3176. {
  3177. let fObj = form.find('wz-toggle-switch');
  3178. for(loop = 0; loop < nNodes; ++loop)
  3179. {
  3180. if(uroRTCClone.ClosedNodes[loop] === true)
  3181. {
  3182. fObj[loop].click();
  3183. }
  3184. }
  3185. }
  3186. if(uroUtils.GetCBChecked('_cbAutoApplyClonedClosure') == true)
  3187. {
  3188. window.setTimeout(uroRTCClone.ClickSave,100);
  3189. }
  3190.  
  3191. uroRTCClone.PendingClone = -1;
  3192. },
  3193. ClickSave: function()
  3194. {
  3195. document.getElementsByClassName('closures')[0].getElementsByClassName('save-button')[0].click();
  3196. },
  3197. Copy: function()
  3198. {
  3199. // grab the current closure details from the UI...
  3200. uroRTCClone.Reason = uroGetShadowElementProperty('closure_reason', 'input', 'value');
  3201. uroRTCClone.Direction = uroGetElementProperty('closure_direction', 0, 'value');
  3202. uroRTCClone.StartDate = uroGetElementProperty('closure_startDate', 0, 'value');
  3203. uroRTCClone.StartTime = document.querySelector('.start-date-form-group').querySelector('.time-picker-input').value;
  3204. uroRTCClone.EndDate = uroGetElementProperty('closure_endDate', 0, 'value');
  3205. uroRTCClone.EndTime = document.querySelector('.end-date-form-group').querySelector('.time-picker-input').value;
  3206. uroRTCClone.Event = uroGetElementProperty('closure_eventId', 0, 'value');
  3207. uroRTCClone.IgnoreTraffic = uroGetElementProperty('closure_permanent', 0, 'checked');
  3208. uroRTCClone.ClosedNodes = [];
  3209. let nNodes = document.getElementsByClassName('fromNodeClosed').length;
  3210. if(nNodes > 0)
  3211. {
  3212. for(let loop = 0; loop < nNodes; ++loop)
  3213. {
  3214. uroRTCClone.ClosedNodes.push(document.getElementsByClassName('fromNodeClosed')[loop].checked);
  3215. }
  3216. }
  3217.  
  3218. document.getElementsByClassName('closures')[0].getElementsByClassName('cancel-button')[0].click();
  3219.  
  3220. // auto-increment the start and end dates
  3221. uroRTCClone.StartDate = uroIncrementClosureDate(uroRTCClone.StartDate,uroRTCClone.PendingCloneIncrement);
  3222. uroRTCClone.EndDate = uroIncrementClosureDate(uroRTCClone.EndDate,uroRTCClone.PendingCloneIncrement);
  3223.  
  3224. uroRTCClone.PendingClone = -2;
  3225. },
  3226. Clone: function()
  3227. {
  3228. uroRTCClone.PendingCloneIncrement = parseInt(this.id.split('-')[1]);
  3229. uroRTCClone.PendingClone = parseInt(this.id.split('-')[2]);
  3230. },
  3231. DeleteNextOnList: function()
  3232. {
  3233. let nClosures = document.querySelectorAll('.closure-item.is-editable').length;
  3234. if(nClosures > 0)
  3235. {
  3236. if (nClosures != uroRTCClone.ToDelete)
  3237. {
  3238. uroRTCClone.ToDelete = nClosures;
  3239. let ctObj = document.querySelector('.closure-item.is-editable');
  3240. let deleteMenuEntry = ctObj.querySelector('wz-menu-item.delete');
  3241. if(deleteMenuEntry !== null)
  3242. {
  3243. deleteMenuEntry.click();
  3244. }
  3245. }
  3246. window.setTimeout(uroRTCClone.DeleteNextOnList,100);
  3247. }
  3248. else
  3249. {
  3250. uroRTCClone.ConfirmDelete = true;
  3251. }
  3252. },
  3253. DeleteAll: function()
  3254. {
  3255. uroRTCClone.ConfirmDelete = true;
  3256. uroAlertBox.Show("fa-warning", "URO+ Warning", I18n.lookup("closures.delete_confirm_no_reason")+' ('+I18n.lookup("closures.apply_to_all")+')', true, "Yes", "No", uroRTCClone.DeleteAllAction, null);
  3257. },
  3258. DeleteAllAction: function()
  3259. {
  3260. uroRTCClone.ConfirmDelete = false;
  3261. let nClosures = document.getElementsByClassName('closure-item').length;
  3262. if(nClosures > 0)
  3263. {
  3264. uroRTCClone.ToDelete = -1;
  3265. uroRTCClone.DeleteNextOnList();
  3266. }
  3267. else
  3268. {
  3269. uroRTCClone.ConfirmDelete = true;
  3270. }
  3271. }
  3272. };
  3273. const uroOWL = /// camera watchlist
  3274. {
  3275. CWLGroups : [],
  3276. CamWatchObjects : [],
  3277.  
  3278. GroupObj: function(groupID, groupName, groupCollapsed)
  3279. {
  3280. groupID = uroUtils.TypeCast(groupID);
  3281. this.groupID = groupID;
  3282. this.groupName = groupName;
  3283. this.groupCount = 0;
  3284. this.groupCollapsed = groupCollapsed;
  3285. },
  3286. CamWatchObjCheckProps: function(type, azymuth, speed, lat, lon)
  3287. {
  3288. if(type !== null) type = uroUtils.TypeCast(type);
  3289. if(azymuth !== null) azymuth = uroUtils.Truncate(uroUtils.TypeCast(azymuth)%360);
  3290. if(speed !== null) speed = uroUtils.Truncate(uroUtils.TypeCast(speed));
  3291. if(lat !== null) lat = uroUtils.Truncate(uroUtils.TypeCast(lat));
  3292. if(lon !== null) lon = uroUtils.Truncate(uroUtils.TypeCast(lon));
  3293.  
  3294. this.type = type;
  3295. this.azymuth = azymuth;
  3296. this.speed = speed;
  3297. this.lat = lat;
  3298. this.lon = lon;
  3299. },
  3300. CamWatchObj: function(persistent, fid, lon, lat, type, azymuth, speed, groupID, server)
  3301. {
  3302. fid = uroUtils.TypeCast(fid);
  3303. groupID = uroUtils.TypeCast(groupID);
  3304. if(typeof persistent == "string") persistent = (persistent == "true");
  3305. if(server === "undefined") server = "??";
  3306.  
  3307. this.fid = fid;
  3308. this.persistent = persistent;
  3309. this.loaded = false;
  3310. this.server = server;
  3311. this.groupID = groupID;
  3312. this.watch = new uroOWL.CamWatchObjCheckProps(type, azymuth, speed, lat, lon);
  3313. this.current = new uroOWL.CamWatchObjCheckProps(null, null, null, null, null);
  3314. },
  3315. CamDataChanged: function(idx)
  3316. {
  3317. let camObj = uroOWL.CamWatchObjects[idx];
  3318. if(camObj.loaded === false) return false;
  3319. if(camObj.current.type != camObj.watch.type) return true;
  3320. if(camObj.current.azymuth != camObj.watch.azymuth) return true;
  3321. if(camObj.current.speed != camObj.watch.speed) return true;
  3322. if(camObj.current.lat != camObj.watch.lat) return true;
  3323. if(camObj.current.lon != camObj.watch.lon) return true;
  3324. return false;
  3325. },
  3326. FindCWLGroupByIdx: function(groupIdx)
  3327. {
  3328. let groupName = '';
  3329. for(let loop=0;loop<uroOWL.CWLGroups.length;loop++)
  3330. {
  3331. if(uroOWL.CWLGroups[loop].groupID == groupIdx)
  3332. {
  3333. groupName = uroOWL.CWLGroups[loop].groupName;
  3334. break;
  3335. }
  3336. }
  3337. return groupName;
  3338. },
  3339. IsCamOnWatchList: function(fid)
  3340. {
  3341. for(let loop=0;loop<uroOWL.CamWatchObjects.length;loop++)
  3342. {
  3343. if(uroOWL.CamWatchObjects[loop].fid == fid) return loop;
  3344. }
  3345. return -1;
  3346. },
  3347. AddCurrentCamWatchData: function(idx, lat, lon, type, azymuth, speed, server)
  3348. {
  3349. let camObj = uroOWL.CamWatchObjects[idx];
  3350. camObj.loaded = true;
  3351. camObj.server = server;
  3352. camObj.current = new uroOWL.CamWatchObjCheckProps(type, azymuth, speed, lat, lon);
  3353. return(uroOWL.CamDataChanged(idx));
  3354. },
  3355. AddCamToWatchList: function()
  3356. {
  3357. if(uroOWL.IsCamOnWatchList(uroShownFID) == -1)
  3358. {
  3359. let camObj = W.model.cameras.objects[uroShownFID];
  3360. uroOWL.CamWatchObjects.push(new uroOWL.CamWatchObj(true, uroShownFID, camObj.geometry.x, camObj.geometry.y, camObj.attributes.type, camObj.attributes.azymuth, camObj.attributes.speed, 0, W.app.getAppRegionCode()));
  3361. uroOWL.AddCurrentCamWatchData(uroOWL.CamWatchObjects.length-1, camObj.geometry.y, camObj.geometry.x, camObj.attributes.type, camObj.attributes.azymuth, camObj.attributes.speed, W.app.getAppRegionCode());
  3362. uroDBG.AddLog('added camera '+uroShownFID+' to watchlist');
  3363. uroTabs.PopulateOWL();
  3364. }
  3365. },
  3366. RemoveCamFromWatchList: function()
  3367. {
  3368. let camidx = uroOWL.IsCamOnWatchList(uroShownFID);
  3369. if(camidx != -1)
  3370. {
  3371. uroOWL.CamWatchObjects.splice(camidx,1);
  3372. uroDBG.AddLog('removed camera '+uroShownFID+' from watchlist');
  3373. uroTabs.PopulateOWL();
  3374. }
  3375. },
  3376. UpdateCamWatchList: function()
  3377. {
  3378. let camIdx = uroOWL.IsCamOnWatchList(uroShownFID);
  3379. if(camIdx != -1)
  3380. {
  3381. let camObj = W.model.cameras.objects[uroShownFID];
  3382. uroOWL.CamWatchObjects[camIdx].watch = new uroOWL.CamWatchObjCheckProps(camObj.attributes.type, camObj.attributes.azymuth, camObj.attributes.speed, camObj.geometry.y, camObj.geometry.x);
  3383. }
  3384. },
  3385. ClearCamWatchList: function()
  3386. {
  3387. uroAlertBox.Show("fa-warning", "URO+ Warning", "Removing all cameras from the OWL <b>cannot</b> be undone.<br>Are you <i>sure</i> you want to do this?", true, "Delete ALL Cameras", "Keep Cameras", uroOWL.ClearCamWatchListAction, null);
  3388. },
  3389. ClearCamWatchListAction: function()
  3390. {
  3391. uroOWL.CamWatchObjects = [];
  3392. uroTabs.PopulateOWL();
  3393. },
  3394. RetrieveCameras: function(lat, lon)
  3395. {
  3396. let camPos = new OpenLayers.LonLat();
  3397. let camChanged = false;
  3398.  
  3399. camPos.lon = lon;
  3400. camPos.lat = lat;
  3401. camPos = uroUtils.ConvertMercatorToWGS84(camPos);
  3402.  
  3403. let camURL = 'https://' + document.location.host;
  3404. camURL += W.Config.api_base;
  3405. camURL += '/Features?language=en&cameras=true&bbox=';
  3406. let latl = camPos.lat - 0.25;
  3407. let latu = camPos.lat + 0.25;
  3408. let lonl = camPos.lon - 0.25;
  3409. let lonr = camPos.lon + 0.25;
  3410. camURL += lonl+','+latl+','+lonr+','+latu;
  3411. uroDBG.AddLog('retrieving camera data around '+camPos.lon+','+camPos.lat);
  3412.  
  3413. let camReq = new XMLHttpRequest();
  3414. camReq.open('GET',camURL,false);
  3415. try
  3416. {
  3417. camReq.send();
  3418. uroDBG.AddLog('response '+camReq.status+' received for camera data request');
  3419. if (camReq.status === 200)
  3420. {
  3421. let camData = JSON.parse(camReq.responseText);
  3422. for(let camIdx = 0; camIdx < camData.cameras.objects.length; camIdx++)
  3423. {
  3424. let camObj = camData.cameras.objects[camIdx];
  3425. let listIdx = uroOWL.IsCamOnWatchList(camObj.id);
  3426. if(listIdx != -1)
  3427. {
  3428. camPos.lon = camObj.geometry.coordinates[0];
  3429. camPos.lat = camObj.geometry.coordinates[1];
  3430. camPos = uroUtils.ConvertWGS84ToMercator(camPos);
  3431. camPos.lon = uroUtils.Truncate(camPos.lon);
  3432. camPos.lat = uroUtils.Truncate(camPos.lat);
  3433. camChanged = (uroOWL.AddCurrentCamWatchData(listIdx, camPos.lat, camPos.lon, camObj.type, camObj.azymuth, camObj.speed, W.app.getAppRegionCode()) || camChanged);
  3434. }
  3435. }
  3436. }
  3437. else
  3438. {
  3439. uroDBG.AddLog('camera data request failed (status != 200)');
  3440. }
  3441. }
  3442. catch(err)
  3443. {
  3444. uroDBG.AddLog('camera data request failed (exception '+err+' caught)');
  3445. }
  3446. return camChanged;
  3447. },
  3448. GetCurrentCamWatchListObjects: function()
  3449. {
  3450. let camChanged = false;
  3451. let camsChanged = [];
  3452. let camsDeleted = [];
  3453. let camidx;
  3454. let camObj;
  3455. for(camidx=0;camidx<uroOWL.CamWatchObjects.length;camidx++)
  3456. {
  3457. camObj = uroOWL.CamWatchObjects[camidx];
  3458. if((camObj.loaded === false) && ((camObj.server == W.app.getAppRegionCode()) || (camObj.server == '??')))
  3459. {
  3460. if(typeof W.model.cameras.objects[camObj.fid] == 'object')
  3461. {
  3462. if(W.model.cameras.objects[camObj.fid].state != "Delete")
  3463. {
  3464. let wazeObj = W.model.cameras.objects[camObj.fid];
  3465. camChanged = (uroOWL.AddCurrentCamWatchData(camidx, wazeObj.geometry.y, wazeObj.geometry.x, wazeObj.attributes.type, wazeObj.attributes.azymuth, wazeObj.attributes.speed, W.app.getAppRegionCode()) || camChanged);
  3466. }
  3467. else
  3468. {
  3469. camChanged = (uroOWL.RetrieveCameras(camObj.watch.lat, camObj.watch.lon) || camChanged);
  3470. }
  3471. }
  3472. else
  3473. {
  3474. camChanged = (uroOWL.RetrieveCameras(camObj.watch.lat, camObj.watch.lon) || camChanged);
  3475. }
  3476. }
  3477. }
  3478.  
  3479. if(camChanged)
  3480. {
  3481. for(camidx=0;camidx<uroOWL.CamWatchObjects.length;camidx++)
  3482. {
  3483. if(uroOWL.CamDataChanged(camidx))
  3484. {
  3485. camsChanged.push(uroOWL.CamWatchObjects[camidx]);
  3486. }
  3487. }
  3488. }
  3489.  
  3490. for(camidx=0;camidx<uroOWL.CamWatchObjects.length;camidx++)
  3491. {
  3492. camObj = uroOWL.CamWatchObjects[camidx];
  3493. if((camObj.loaded === false) && (camObj.server == W.app.getAppRegionCode()))
  3494. {
  3495. camsDeleted.push(camObj);
  3496. }
  3497. }
  3498. if((camsChanged.length > 0) || (camsDeleted.length > 0))
  3499. {
  3500. let alertStr = '';
  3501. for(camidx=0;camidx<camsChanged.length;camidx++)
  3502. {
  3503. alertStr += 'Camera ID '+camsChanged[camidx].fid+' in group "'+uroOWL.FindCWLGroupByIdx(camsChanged[camidx].groupID)+'" has been changed<br>';
  3504. }
  3505. alertStr += '<br>';
  3506. for(camidx=0;camidx<camsDeleted.length;camidx++)
  3507. {
  3508. alertStr += 'Camera ID '+camsDeleted[camidx].fid+' in group "'+uroOWL.FindCWLGroupByIdx(camsDeleted[camidx].groupID)+'" has been deleted<br>';
  3509. }
  3510. uroAlertBox.Show("fa-info-circle", "URO+ Camera Watchlist Alert", alertStr, false, "OK", null, null, null);
  3511. }
  3512. },
  3513. ClearDeletedCameras: function()
  3514. {
  3515. for(let camidx=uroOWL.CamWatchObjects.length-1;camidx>=0;camidx--)
  3516. {
  3517. if(uroOWL.CamWatchObjects[camidx].loaded === false)
  3518. {
  3519. uroShownFID = uroOWL.CamWatchObjects[camidx].fid;
  3520. uroOWL.RemoveCamFromWatchList();
  3521. }
  3522. }
  3523. },
  3524. AcceptCameraChanges: function()
  3525. {
  3526. for(let camidx=0; camidx < uroOWL.CamWatchObjects.length; camidx++)
  3527. {
  3528. if(uroOWL.CamDataChanged(camidx))
  3529. {
  3530. uroOWL.CamWatchObjects[camidx].watch.type = uroOWL.CamWatchObjects[camidx].current.type;
  3531. uroOWL.CamWatchObjects[camidx].watch.azymuth = uroOWL.CamWatchObjects[camidx].current.azymuth;
  3532. uroOWL.CamWatchObjects[camidx].watch.speed = uroOWL.CamWatchObjects[camidx].current.speed;
  3533. uroOWL.CamWatchObjects[camidx].watch.lat = uroOWL.CamWatchObjects[camidx].current.lat;
  3534. uroOWL.CamWatchObjects[camidx].watch.lon = uroOWL.CamWatchObjects[camidx].current.lon;
  3535. }
  3536. }
  3537. uroTabs.PopulateOWL();
  3538. },
  3539. ClearUnknownServerCameras: function()
  3540. {
  3541. let confirmMsg = '<p>Cameras with an unknown server <i>cannot</i> be automatically verified by URO+</p>';
  3542. confirmMsg += 'It is recommended that you manually load WME from each server (World, USA/Canada and Israel) to give URO+ a chance of locating these cameras.<br>';
  3543. confirmMsg += 'If the cameras then continue to show up as an unknown server, it is safe to delete them...<br><br>';
  3544. confirmMsg += 'Do you still wish to proceed with deleting all unknown server cameras?';
  3545.  
  3546. uroAlertBox.Show("fa-warning", "URO+ Warning", confirmMsg, true, "Delete unknown cameras", "Keep unknown cameras", uroOWL.ClearUnknownServerCamerasAction, null);
  3547. },
  3548. ClearUnknownServerCamerasAction: function()
  3549. {
  3550. for(let camidx=uroOWL.CamWatchObjects.length-1;camidx>=0;camidx--)
  3551. {
  3552. if(uroOWL.CamWatchObjects[camidx].server == '??')
  3553. {
  3554. uroShownFID = uroOWL.CamWatchObjects[camidx].fid;
  3555. uroOWL.RemoveCamFromWatchList();
  3556. }
  3557. }
  3558. },
  3559. RescanCamWatchList: function()
  3560. {
  3561. for(let camidx=0;camidx<uroOWL.CamWatchObjects.length;camidx++)
  3562. {
  3563. uroOWL.CamWatchObjects[camidx].loaded = false;
  3564. }
  3565. uroOWL.GetCurrentCamWatchListObjects();
  3566. uroTabs.PopulateOWL();
  3567. },
  3568. GotoCam: function()
  3569. {
  3570. let camidx = this.id.substr(13);
  3571. let camPos = new OpenLayers.LonLat();
  3572. camPos.lon = uroOWL.CamWatchObjects[camidx].watch.lon;
  3573. camPos.lat = uroOWL.CamWatchObjects[camidx].watch.lat;
  3574. W.map.setCenter(camPos,16);
  3575. W.map.camerasLayer.setVisibility(true);
  3576. return false;
  3577. },
  3578. HighlightCWLEntry: function()
  3579. {
  3580. this.style.backgroundColor = '#FFFFAA';
  3581. return false;
  3582. },
  3583. UnhighlightCWLEntry: function()
  3584. {
  3585. let camidx = this.id.substr(8);
  3586. let changed = uroOWL.CamDataChanged(camidx);
  3587. let deleted = (uroOWL.CamWatchObjects[camidx].loaded === false);
  3588.  
  3589. if(uroOWL.CamWatchObjects[camidx].server != W.app.getAppRegionCode())
  3590. {
  3591. if(uroOWL.CamWatchObjects[camidx].server == '??') this.style.backgroundColor = '#A0A0A0';
  3592. else this.style.backgroundColor = '#AAFFAA';
  3593. }
  3594. else if(changed) this.style.backgroundColor = '#AAAAFF';
  3595. else if(deleted) this.style.backgroundColor = '#FFAAAA';
  3596. else this.style.backgroundColor = '#FFFFFF';
  3597. return false;
  3598. },
  3599. CWLIconHighlight: function()
  3600. {
  3601. this.style.color="#0000ff";
  3602. return false;
  3603. },
  3604. CWLIconLowlight: function()
  3605. {
  3606. this.style.color="#ccccff";
  3607. return false;
  3608. },
  3609. PopulateCWLGroupSelect: function()
  3610. {
  3611. let selector = document.getElementById('_uroCWLGroupSelect');
  3612. while(selector.options.length > 0)
  3613. {
  3614. selector.options.remove(0);
  3615. }
  3616. for(let loop=0;loop<uroOWL.CWLGroups.length;loop++)
  3617. {
  3618. let groupObj = uroOWL.CWLGroups[loop];
  3619. if(groupObj.groupID != -1)
  3620. {
  3621. selector.options.add(new Option(groupObj.groupName,groupObj.groupID));
  3622. }
  3623. }
  3624. },
  3625. GetNextCWLGroupID: function()
  3626. {
  3627. let nextID = 1;
  3628. for(let loop=0;loop<uroOWL.CWLGroups.length;loop++)
  3629. {
  3630. if(uroOWL.CWLGroups[loop].groupID >= nextID)
  3631. {
  3632. nextID = uroOWL.CWLGroups[loop].groupID + 1;
  3633. }
  3634. }
  3635. return nextID;
  3636. },
  3637. FindCWLGroupByName: function(groupName)
  3638. {
  3639. let groupID = -1;
  3640. for(let loop=0;loop<uroOWL.CWLGroups.length;loop++)
  3641. {
  3642. if((uroOWL.CWLGroups[loop].groupName == groupName) && (uroOWL.CWLGroups[loop].groupID != -1))
  3643. {
  3644. groupID = uroOWL.CWLGroups[loop].groupID;
  3645. break;
  3646. }
  3647. }
  3648. return groupID;
  3649. },
  3650. AddCWLGroup: function()
  3651. {
  3652. let groupID = uroOWL.GetNextCWLGroupID();
  3653. let groupName = uroUtils.GetElmValue('_uroCWLGroupEntry');
  3654. if(uroOWL.FindCWLGroupByName(groupName) == -1)
  3655. {
  3656. uroOWL.CWLGroups.push(new uroOWL.GroupObj(groupID,groupName,false));
  3657. uroOWL.PopulateCWLGroupSelect();
  3658. }
  3659. },
  3660. RemoveCWLGroup: function()
  3661. {
  3662. let loop;
  3663. let selector = document.getElementById('_uroCWLGroupSelect');
  3664. let groupID = parseInt(selector.selectedOptions[0].value);
  3665. if(groupID === 0) return false; // prevent deletion of the default group
  3666.  
  3667. for(loop=0;loop<uroOWL.CamWatchObjects.length;loop++)
  3668. {
  3669. let cwObj = uroOWL.CamWatchObjects[loop];
  3670. if(cwObj.groupID == groupID)
  3671. {
  3672. cwObj.groupID = 0;
  3673. }
  3674. }
  3675. for(loop=0;loop<uroOWL.CWLGroups.length;loop++)
  3676. {
  3677. let groupObj = uroOWL.CWLGroups[loop];
  3678. if(groupObj.groupID == groupID)
  3679. {
  3680. groupObj.groupID = -1;
  3681. }
  3682. }
  3683. uroTabs.PopulateOWL();
  3684. },
  3685. AssignCameraToGroup: function()
  3686. {
  3687. let camidx = this.id.substr(13);
  3688. let selector = document.getElementById('_uroCWLGroupSelect');
  3689. uroOWL.CamWatchObjects[camidx].groupID = parseInt(selector.selectedOptions[0].value);
  3690. uroTabs.PopulateOWL();
  3691. return false;
  3692. },
  3693. CWLGroupCollapseExpand: function()
  3694. {
  3695. let groupidx = this.id.substr(18);
  3696. if(uroOWL.CWLGroups[groupidx].groupCollapsed === true) uroOWL.CWLGroups[groupidx].groupCollapsed = false;
  3697. else uroOWL.CWLGroups[groupidx].groupCollapsed = true;
  3698. uroTabs.PopulateOWL();
  3699. return false;
  3700. }
  3701. };
  3702. const uroIgnore = // ignore list
  3703. {
  3704. IsOnList: function(fid)
  3705. {
  3706. if(sessionStorage.UROverview_FID_IgnoreList.indexOf('fid:'+fid) == -1) return false;
  3707. else return true;
  3708. },
  3709. EnableControls: function()
  3710. {
  3711. let btnState = "visible";
  3712. if(sessionStorage.UROverview_FID_IgnoreList === '')
  3713. {
  3714. btnState = "hidden";
  3715. }
  3716. try
  3717. {
  3718. document.getElementById('_btnUndoLastHide').style.visibility = btnState;
  3719. document.getElementById('_btnClearSessionHides').style.visibility = btnState;
  3720. uroFilterItems();
  3721. }
  3722. catch(err)
  3723. {
  3724. uroDBG.AddLog('exception thrown in uroIgnore.EnableControls()');
  3725. }
  3726. },
  3727. Add: function()
  3728. {
  3729. if(!uroIgnore.IsOnList(uroShownFID))
  3730. {
  3731. sessionStorage.UROverview_FID_IgnoreList += 'fid:'+uroShownFID;
  3732. uroDBG.AddLog('added fid '+uroShownFID+' to ignore list');
  3733. uroDBG.AddLog(sessionStorage.UROverview_FID_IgnoreList);
  3734. uroDiv.style.visibility = 'hidden';
  3735. uroIgnore.EnableControls();
  3736.  
  3737. W.map.events.register("mousemove", null, uroFilterItemsOnMove);
  3738. }
  3739. return false;
  3740. },
  3741. RemoveLastAdded: function()
  3742. {
  3743. let ignorelist = sessionStorage.UROverview_FID_IgnoreList;
  3744. let fidpos = ignorelist.lastIndexOf('fid:');
  3745. if(fidpos != -1)
  3746. {
  3747. ignorelist = ignorelist.slice(0,fidpos);
  3748. sessionStorage.UROverview_FID_IgnoreList = ignorelist;
  3749. uroDBG.AddLog('removed last fid from ignore list');
  3750. uroDBG.AddLog(sessionStorage.UROverview_FID_IgnoreList);
  3751. uroIgnore.EnableControls();
  3752. }
  3753. },
  3754. RemoveAll: function()
  3755. {
  3756. sessionStorage.UROverview_FID_IgnoreList = '';
  3757. uroIgnore.EnableControls();
  3758. }
  3759. };
  3760. const uroPopup = // map object popup handling
  3761. {
  3762. hasIgnoreLink : null,
  3763. hasDeleteLink : null,
  3764. hasAddWatchLink : null,
  3765. hasRemoveWatchLink : null,
  3766. hasUpdateWatchLink : null,
  3767. hasRecentreSessionLink : null,
  3768. hasOpenInNewTabLink : null,
  3769.  
  3770. isVenue : null,
  3771. isMapComment : null,
  3772. isUR : null,
  3773. isProblem : null,
  3774. isTurnProb : null,
  3775. isPlaceUpdate : null,
  3776. timer : -2,
  3777. autoHideTimer : 0,
  3778. mouseIn : false,
  3779. shown : false,
  3780. shownType : null,
  3781. newType : null,
  3782. hovered : null,
  3783. unstackedX : null,
  3784. unstackedY : null,
  3785. renderIntent : null,
  3786. pX : null,
  3787. pY : null,
  3788. suppressed : false,
  3789.  
  3790. GetFormattedLocks: function(attrs)
  3791. {
  3792. let autoLock = attrs.rank;
  3793. let userLock = attrs.lockRank;
  3794. let retval = '<b>' + I18n.lookup("edit.segment.fields.lock") + ': </b>';
  3795. if(userLock !== null)
  3796. {
  3797. retval += 'M' + (userLock+1) + ' / ';
  3798. }
  3799. retval += 'A' + (autoLock+1);
  3800. return retval;
  3801. },
  3802. UR: function()
  3803. {
  3804. let result = '';
  3805.  
  3806. uroPopup.unstackedX = uroUtils.ParsePxString(uroMarkers.elm.style.left);
  3807. uroPopup.unstackedY = uroUtils.ParsePxString(uroMarkers.elm.style.top);
  3808. // check for stacking...
  3809. if(uroShownFID != uroMarkers.id)
  3810. {
  3811. uroCheckStacking(uroLayers.ID.UR,uroMarkers.id, uroPopup.unstackedX, uroPopup.unstackedY);
  3812. }
  3813.  
  3814. if(uroUtils.GetCBChecked('_cbInhibitURPopup') === false)
  3815. {
  3816. if(uroMousedOverMapComment !== null)
  3817. {
  3818. uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for UR highlight');
  3819. uroMousedOverOtherObjectWithinMapComment = true;
  3820. }
  3821.  
  3822. uroPopup.isUR = true;
  3823. uroPopup.newPopupType = uroMarkers.type;
  3824. let ureq = W.model.mapUpdateRequests.objects[uroMarkers.id];
  3825.  
  3826. if(ureq.attributes != undefined)
  3827. {
  3828. uroFID = uroMarkers.id;
  3829.  
  3830. uroDBG.AddLog('building popup for UR '+uroMarkers.id);
  3831. result = '<b>Update Request ('+uroMarkers.id+'): ' + I18n.lookup("update_requests.types." + ureq.attributes.type) + '</b><br>';
  3832.  
  3833. result += uroUtils.Clickify(ureq.attributes.description, '<br>');
  3834. let uroDaysOld = uroUtils.GetURAge(ureq,0,false);
  3835. let uroSubmittedTS = uroUtils.GetURAge(ureq,0,true);
  3836. if(uroSubmittedTS != -1)
  3837. {
  3838. uroSubmittedTS = uroUtils.GetDateTimeString(uroSubmittedTS);
  3839. }
  3840. if(uroDaysOld != -1)
  3841. {
  3842. result += '<i>Submitted ' + uroUtils.ParseDaysAgo(uroDaysOld) + ' ';
  3843. if(uroSubmittedTS != -1) result += '(' + uroSubmittedTS + ') ';
  3844. if(ureq.attributes.guestUserName != null)
  3845. {
  3846. result += 'via Livemap';
  3847. if(ureq.attributes.guestUserName !== '')
  3848. {
  3849. result += ' by '+ureq.attributes.guestUserName.replace(/<\/?[^>]+(>|$)/g, "");
  3850. }
  3851. }
  3852. result += '</i>';
  3853. }
  3854. if(ureq.attributes.resolvedOn !== null)
  3855. {
  3856. let daysResolved = uroUtils.GetURAge(ureq,1,false);
  3857. let uroResolvedTS = uroUtils.GetURAge(ureq,1,true);
  3858. if(uroResolvedTS != -1)
  3859. {
  3860. uroResolvedTS = uroUtils.GetDateTimeString(uroResolvedTS);
  3861. }
  3862.  
  3863. if(daysResolved != -1)
  3864. {
  3865. result += '<br><i>Closed ' + uroUtils.ParseDaysAgo(daysResolved) + ' ';
  3866. if(uroResolvedTS != -1) result += '(' + uroResolvedTS + ')</i>';
  3867.  
  3868. result += '<br><i>Marked as ';
  3869. if(ureq.attributes.resolution === 0) result += 'solved';
  3870. else if(ureq.attributes.resolution == 1) result += 'not identified';
  3871. else result += 'unknown';
  3872. if(ureq.attributes.resolvedBy !== null)
  3873. {
  3874. result += ' by '+uroUtils.GetUserNameAndRank(ureq.attributes.resolvedBy);
  3875. }
  3876. result += '</i>';
  3877. }
  3878. }
  3879. if(W.model.updateRequestSessions.objects[uroMarkers.id] != null)
  3880. {
  3881. let hasMyComments = uroURHasMyComments(uroMarkers.id);
  3882. let nComments = W.model.updateRequestSessions.objects[uroMarkers.id].attributes.comments.length;
  3883. result += '<br>' + nComments + ' comment';
  3884. if(nComments != 1) result += 's';
  3885. if((hasMyComments === false) && (nComments > 0)) result += ' (none by me)';
  3886. if(nComments > 0)
  3887. {
  3888. let commentDaysOld = uroUtils.GetCommentAge(W.model.updateRequestSessions.objects[uroMarkers.id].attributes.comments[nComments-1]);
  3889. if(commentDaysOld != -1)
  3890. {
  3891. result += ', last update '+uroUtils.ParseDaysAgo(commentDaysOld);
  3892. }
  3893. }
  3894. }
  3895. if(uroURDupes.length > 0)
  3896. {
  3897. let thisID = parseInt(uroMarkers.id);
  3898. for(let i = 0; i < uroURDupes.length; ++i)
  3899. {
  3900. if(uroURDupes[i][0] === thisID)
  3901. {
  3902. result += '<br><br>Duplicate of: ';
  3903. let dupes = 0;
  3904. for(let j = 0; j < uroURDupes[i][1].length; ++j)
  3905. {
  3906. if(uroURDupes[i][1][j] !== thisID)
  3907. {
  3908. if(dupes > 0)
  3909. {
  3910. result += ', ';
  3911. }
  3912. result += uroURDupes[i][1][j];
  3913. ++dupes;
  3914. }
  3915. }
  3916. }
  3917. }
  3918. }
  3919. uroPopup.result += result;
  3920. uroPopup.UMPExtras(ureq);
  3921. uroPopup.Show();
  3922. }
  3923. }
  3924. },
  3925. MP: function()
  3926. {
  3927. let result = '';
  3928.  
  3929. uroPopup.unstackedX = uroUtils.ParsePxString(uroMarkers.elm.style.left);
  3930. uroPopup.unstackedY = uroUtils.ParsePxString(uroMarkers.elm.style.top);
  3931. // check for stacking...
  3932. if(uroShownFID != uroMarkers.id)
  3933. {
  3934. ////uroCheckStacking(uroLayers.ID.MP,uroMarkers.id, uroPopup.unstackedX, uroPopup.unstackedY);
  3935. }
  3936.  
  3937. if(uroUtils.GetCBChecked('_cbInhibitMPPopup') === false)
  3938. {
  3939. if(uroMousedOverMapComment !== null)
  3940. {
  3941. uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for MP highlight');
  3942. uroMousedOverOtherObjectWithinMapComment = true;
  3943. }
  3944.  
  3945. uroPopup.isProblem = true;
  3946. uroPopup.newPopupType = uroMarkers.type;
  3947. let ureq = W.model.mapProblems.objects[uroMarkers.id];
  3948.  
  3949. uroFID = uroMarkers.id;
  3950. uroDBG.AddLog('building popup for problem '+uroMarkers.id);
  3951. if(uroPopup.isTurnProb) result = '<b>Turn Problem ('+uroMarkers.id+'): ' + I18n.lookup("problems.types.turn.title");
  3952. else
  3953. {
  3954. result = '<b>Map Problem ('+uroMarkers.id+'): ';
  3955.  
  3956. let problemType = ureq.attributes.subType;
  3957. if(problemType == 300)
  3958. {
  3959. result += I18n.lookup("problems.panel.closure.title");
  3960. }
  3961. else
  3962. {
  3963. if(I18n.lookup("problems.types." + problemType) === undefined) result += 'Unknown problem type ('+problemType+')';
  3964. else result += I18n.lookup("problems.types." + problemType + ".title");
  3965. }
  3966. }
  3967. result += '</b><br>';
  3968.  
  3969. if(ureq.attributes.description != null)
  3970. {
  3971. result += 'Description: ' + ureq.attributes.description + '<br>';
  3972. }
  3973. if(ureq.attributes.extraInfo != null)
  3974. {
  3975. result += 'ExtraInfo: ' + uroUtils.Clickify(ureq.attributes.extraInfo, '<br>');
  3976. }
  3977. if(ureq.attributes.provider != null)
  3978. {
  3979. result += 'Provider: ' + ureq.attributes.provider + '<br>';
  3980. }
  3981. if(ureq.attributes.startTime != null)
  3982. {
  3983. result += 'From: ' + uroUtils.GetDateTimeString(ureq.attributes.startTime) + '<br>';
  3984. }
  3985. if(ureq.attributes.endTime != null)
  3986. {
  3987. result += 'To: ' + uroUtils.GetDateTimeString(ureq.attributes.endTime) + '<br>';
  3988. }
  3989. if(ureq.attributes.resolvedOn != null)
  3990. {
  3991. let daysResolved = uroUtils.GetURAge(ureq,1,false);
  3992. if(daysResolved != -1)
  3993. {
  3994. result += '<br><i>Closed ' + uroUtils.ParseDaysAgo(daysResolved) + ' ';
  3995. if(ureq.attributes.resolvedBy != null)
  3996. {
  3997. result += ' by '+uroUtils.GetUserNameAndRank(ureq.attributes.resolvedBy);
  3998. }
  3999.  
  4000. if((ureq.attributes.open === true) && (ureq.attributes.resolvedOn != null))
  4001. {
  4002. result += '<br>Reopened by Waze';
  4003. }
  4004. result += '</i>';
  4005. }
  4006. }
  4007. uroPopup.result += result;
  4008. uroPopup.UMPExtras(ureq);
  4009. }
  4010. },
  4011. PUR: function()
  4012. {
  4013. if(uroMarkers?.elm?.style == undefined)
  4014. {
  4015. return;
  4016. }
  4017.  
  4018. let result = '';
  4019.  
  4020. uroPopup.unstackedX = uroUtils.ParsePxString(uroMarkers.elm.style.left);
  4021. uroPopup.unstackedY = uroUtils.ParsePxString(uroMarkers.elm.style.top);
  4022.  
  4023. if(uroShownFID != uroMarkers.id)
  4024. {
  4025. // check for stacking...
  4026. uroCheckStacking(uroMarkers.type, uroMarkers.id, uroPopup.unstackedX, uroPopup.unstackedY);
  4027. }
  4028.  
  4029. if(uroUtils.GetCBChecked('_cbInhibitPUPopup') === false)
  4030. {
  4031. let ureq = W.model.venues.objects[uroMarkers.id];
  4032.  
  4033. uroPopup.newPopupType = uroMarkers.type;
  4034. if(uroMousedOverMapComment !== null)
  4035. {
  4036. uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for PUR highlight');
  4037. uroMousedOverOtherObjectWithinMapComment = true;
  4038. }
  4039.  
  4040. uroPopup.isPlaceUpdate = true;
  4041.  
  4042. uroFID = uroMarkers.id;
  4043.  
  4044. if(uroMarkers.type == uroLayers.ID.PUR)
  4045. {
  4046. uroDBG.AddLog('building popup for placeUpdate '+uroMarkers.id);
  4047. }
  4048. else if(uroMarkers.type == uroLayers.ID.RPUR)
  4049. {
  4050. uroDBG.AddLog('building popup for residentialPlaceUpdate '+uroMarkers.id);
  4051. }
  4052. else
  4053. {
  4054. uroDBG.AddLog('building popup for parkingPlaceUpdate '+uroMarkers.id);
  4055. }
  4056.  
  4057. result = '<b>';
  4058. if(ureq.attributes.name === '') result += 'Unnamed landmark';
  4059. else result += ureq.attributes.name;
  4060. result += '</b><br>';
  4061.  
  4062. result += '<ul>';
  4063. for(let idx = 0; idx < ureq.attributes.categories.length; idx++)
  4064. {
  4065. result += '<li>' + I18n.lookup("venues.categories." + ureq.attributes.categories[idx]);
  4066. }
  4067. result += '</ul>';
  4068.  
  4069. if(ureq.attributes.residential === true)
  4070. {
  4071. result += '<i>Residential</i>';
  4072. }
  4073.  
  4074. let daysOld = uroUtils.GetPURAge(ureq);
  4075. if(daysOld != -1)
  4076. {
  4077. result += '<br><i>Submitted '+uroUtils.ParseDaysAgo(daysOld)+'</i>';
  4078. }
  4079.  
  4080. uroPopup.result += result;
  4081. uroPopup.UMPExtras(ureq);
  4082. }
  4083. },
  4084. Venue: function()
  4085. {
  4086. if(uroUtils.GetCBChecked('_cbInhibitLandmarkPopup') === false)
  4087. {
  4088. let result = '';
  4089. let navpointPos=new OpenLayers.LonLat();
  4090. {
  4091. if(uroPopup.renderIntent === 'highlight')
  4092. {
  4093. if(uroUtils.GetExtent().intersectsBounds(uroMarkers.obj.attributes.geometry.getBounds()))
  4094. {
  4095. if(uroMousedOverMapComment !== null)
  4096. {
  4097. uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for place highlight');
  4098. uroMousedOverOtherObjectWithinMapComment = true;
  4099. }
  4100.  
  4101. if(uroPopup.newPopupType === null)
  4102. {
  4103. uroFID = uroMarkers.obj.attributes.id;
  4104. uroDBG.AddLog('building popup for place '+uroFID);
  4105.  
  4106. navpointPos = uroGetVenueNavPoint(uroFID);
  4107. result += '<b>';
  4108. if(uroMarkers.obj.attributes.name === '')
  4109. {
  4110. if(uroMarkers.obj.attributes.residential === true) result += '<i>Residential</i>';
  4111. else result += '<i>Unnamed</i>';
  4112. }
  4113. else result += uroUtils.Clickify(uroMarkers.obj.attributes.name, '');
  4114. if(uroMarkers.obj.attributes.externalProviderIDs.length > 0)
  4115. {
  4116. result += ' <i>(linked)</i>';
  4117. }
  4118. if(uroMarkers.obj.attributes.adLocked)
  4119. {
  4120. result += ' <i>(AdLocked)</i>';
  4121. }
  4122. result += '</b><br>';
  4123. if(uroMarkers.obj.attributes.brand !== null)
  4124. {
  4125. result += '<i>Brand: ' + uroMarkers.obj.attributes.brand + '</i><br>';
  4126. }
  4127. let vDesc = uroMarkers.obj.attributes.description;
  4128. if(vDesc !== '')
  4129. {
  4130. result += '"<i>' + uroUtils.Clickify(vDesc, '') + '</i>"<br>';
  4131. }
  4132.  
  4133. let userLock = uroMarkers.obj.attributes.lockRank;
  4134. result += '<b>Lock: </b>' + (userLock+1);
  4135.  
  4136. result += '<hr>';
  4137. result += uroGetAddress(uroMarkers.obj.attributes.streetID, uroMarkers.obj.attributes.houseNumber, false, false, false);
  4138. result += '<ul>';
  4139. for(let idx = 0; idx < uroMarkers.obj.attributes.categories.length; idx++)
  4140. {
  4141. result += '<li>' + I18n.lookup("venues.categories." + uroMarkers.obj.attributes.categories[idx]);
  4142. }
  4143. result += '</ul>';
  4144.  
  4145. let npLink = document.location.href;
  4146. let npLayers = '';
  4147. npLink = npLink.substr(0,npLink.indexOf('?zoomLevel'));
  4148. npLink += '?zoomLevel=17&lat='+navpointPos.lat+'&lon='+navpointPos.lon+npLayers;
  4149.  
  4150. let targetTab = "_uroTab_" + Math.round(Math.random()*1000000);
  4151. result += '<hr>Jump to nav point: <a href="'+npLink+'" id="_openInNewTab" target="'+targetTab+'">in new tab</a> - ';
  4152. uroPopup.hasOpenInNewTabLink = true;
  4153. result += '<a href="#" id="_recentreSession">in this tab</a>';
  4154. uroPopup.hasRecentreSessionLink = true;
  4155.  
  4156. uroPopup.newPopupType = 'venue';
  4157. uroPopup.isVenue = true;
  4158.  
  4159. uroPopup.result += result;
  4160. uroPopup.Show();
  4161. }
  4162. else
  4163. {
  4164. let otherID = uroMarkers.obj.attributes.id;
  4165. uroDBG.AddLog('venue '+otherID+' is also highlighted');
  4166. }
  4167. }
  4168. else
  4169. {
  4170. uroDBG.AddLog('landmark '+uroFID+' has renderIntent==highlight but is offscreen... blocking popup');
  4171. }
  4172. }
  4173. }
  4174. }
  4175. },
  4176. Camera: function()
  4177. {
  4178. if(uroUtils.GetCBChecked('_cbInhibitCamPopup') === false)
  4179. {
  4180. let result = '';
  4181. let ureq = uroMarkers.obj;
  4182.  
  4183. if(uroPopup.renderIntent === "highlight")
  4184. {
  4185. if(uroMousedOverMapComment !== null)
  4186. {
  4187. uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for camera highlight');
  4188. uroMousedOverOtherObjectWithinMapComment = true;
  4189. }
  4190. uroPopup.newPopupType = 'camera';
  4191. uroFID = uroMarkers.id;
  4192. uroDBG.AddLog('building popup for camera '+uroFID);
  4193. if(I18n.lookup("edit.camera.fields.type") === undefined)
  4194. {
  4195. result += '<b>Camera: ' + ureq.TYPES[ureq.attributes.type] + '</b>';
  4196. }
  4197. else
  4198. {
  4199. result += '<b>Camera: ' + I18n.lookup("edit.camera.fields.type." + ureq.attributes.type) + '</b>';
  4200. }
  4201. result += '<br>';
  4202. result += '<b>ID:</b> ' + uroFID + ' - ';
  4203. result += uroPopup.GetFormattedLocks(uroMarkers.obj.attributes);
  4204. result += '<br>';
  4205.  
  4206. result += '<b>Created by</b> ';
  4207. let userID;
  4208.  
  4209. if(W.model.users.getByIds([ureq.attributes.createdBy])[0] != null)
  4210. {
  4211. userID = ureq.attributes.createdBy;
  4212. result += uroUtils.GetUserNameAndRank(userID);
  4213. }
  4214. else result += 'unknown';
  4215. result += ', ';
  4216. let camAge = uroUtils.GetCameraAge(ureq,1);
  4217. if(camAge != -1)
  4218. {
  4219. result += uroUtils.ParseDaysAgo(camAge);
  4220. }
  4221. else result += 'unknown days ago';
  4222. result += '<br><b>Updated by</b> ';
  4223. if(W.model.users.getByIds([ureq.attributes.updatedBy])[0] != null)
  4224. {
  4225. userID = ureq.attributes.updatedBy;
  4226. result += uroUtils.GetUserNameAndRank(userID);
  4227. }
  4228. else result += 'unknown';
  4229. result += ', ';
  4230. camAge = uroUtils.GetCameraAge(ureq,0);
  4231. if(camAge != -1)
  4232. {
  4233. result += uroUtils.ParseDaysAgo(camAge);
  4234. }
  4235. else result += 'unknown days ago';
  4236.  
  4237. if(ureq.attributes.type !== 4)
  4238. {
  4239. result += '<br><b>Speed data:</b> ';
  4240. result += uroUtils.GetLocalisedSpeedString(ureq.attributes.speed);
  4241. if(uroIsCamSpeedValid(ureq) === false)
  4242. {
  4243. result += ' (differs to nearest segment)';
  4244. }
  4245. }
  4246. result += '<hr><ul>';
  4247. if(uroOWL.IsCamOnWatchList(uroFID) != -1)
  4248. {
  4249. result += '<li><a href="#" id="_updatewatchlist">Update watchlist entry</a>';
  4250. result += '<li><a href="#" id="_removefromwatchlist">Remove from watchlist</a>';
  4251. uroPopup.hasUpdateWatchLink = true;
  4252. uroPopup.hasRemoveWatchLink = true;
  4253. }
  4254. else
  4255. {
  4256. result += '<li><a href="#" id="_addtowatchlist">Add to watchlist</a>';
  4257. uroPopup.hasAddWatchLink = true;
  4258. }
  4259. if(ureq.attributes.permissions !== 0)
  4260. {
  4261. result += '<li><a href="#" id="_deleteobject">Delete Camera</a>';
  4262. uroPopup.hasDeleteLink = true;
  4263. }
  4264. result += '</ul>';
  4265. uroPopup.result += result;
  4266. uroPopup.Show();
  4267. }
  4268. }
  4269. },
  4270. Comment: function()
  4271. {
  4272. if(uroUtils.GetCBChecked('_cbInhibitMapCommentPopup') === false)
  4273. {
  4274. if(uroMCLayer.name !== 'mapComments')
  4275. {
  4276. uroInit.WazeBits();
  4277. }
  4278. if(uroMCLayer !== null)
  4279. {
  4280. let result = '';
  4281.  
  4282. if(uroPopup.renderIntent == 'highlight')
  4283. {
  4284. let moAttrs = uroMarkers.obj.attributes;
  4285. if(uroUtils.GetExtent().intersectsBounds(moAttrs.geometry.getBounds()))
  4286. {
  4287. if(uroPopup.newPopupType === null)
  4288. {
  4289. if((uroMousedOverMapComment === moAttrs.id) && (uroMousedOverOtherObjectWithinMapComment === true))
  4290. {
  4291. uroDBG.AddLog('inhibit popup for map comment '+uroMousedOverMapComment);
  4292. }
  4293. else
  4294. {
  4295. uroMousedOverOtherObjectWithinMapComment = false;
  4296. if(moAttrs.geometry.id.indexOf('Polygon') !== -1)
  4297. {
  4298. // only capture ID for area comments...
  4299. uroMousedOverMapComment = moAttrs.id;
  4300. }
  4301. uroFID = moAttrs.id;
  4302. uroDBG.AddLog('building popup for map comment '+uroFID);
  4303.  
  4304. result += '<b>';
  4305. if(moAttrs.subject === '')
  4306. {
  4307. result += '<i>No subject</i>';
  4308. }
  4309. else result += uroUtils.Clickify(moAttrs.subject, '');
  4310. result += '</b><br>';
  4311. result += uroUtils.Clickify(moAttrs.body, '<br>');
  4312.  
  4313. let mcDaysOld = uroUtils.GetMCAge(moAttrs, 0, false);
  4314. let mcSubmittedTS = uroUtils.GetMCAge(moAttrs, 0, true);
  4315. if(mcSubmittedTS != -1)
  4316. {
  4317. mcSubmittedTS = uroUtils.GetDateTimeString(mcSubmittedTS);
  4318. }
  4319. if(mcDaysOld != -1)
  4320. {
  4321. result += '<i>Submitted ' + uroUtils.ParseDaysAgo(mcDaysOld) + ' ';
  4322. if(mcSubmittedTS != -1) result += '(' + mcSubmittedTS + ') ';
  4323. if(moAttrs.createdBy != null)
  4324. {
  4325. result += ' by '+uroUtils.GetUserNameAndRank(moAttrs.createdBy);
  4326. }
  4327. result += '</i><br>';
  4328. }
  4329. mcDaysOld = uroUtils.GetMCAge(moAttrs, 1, false);
  4330. mcSubmittedTS = uroUtils.GetMCAge(moAttrs, 1, true);
  4331. if(mcSubmittedTS != -1)
  4332. {
  4333. mcSubmittedTS = uroUtils.GetDateTimeString(mcSubmittedTS);
  4334. }
  4335. if(mcDaysOld != -1)
  4336. {
  4337. result += '<i>Updated ' + uroUtils.ParseDaysAgo(mcDaysOld) + ' ';
  4338. if(mcSubmittedTS != -1) result += '(' + mcSubmittedTS + ') ';
  4339. if(moAttrs.createdBy != null)
  4340. {
  4341. result += ' by '+uroUtils.GetUserNameAndRank(moAttrs.updatedBy);
  4342. }
  4343. result += '</i><br>';
  4344. }
  4345.  
  4346. mcDaysOld = uroUtils.GetMCAge(moAttrs,2,false);
  4347. mcSubmittedTS = uroUtils.GetMCAge(moAttrs,2,true);
  4348. if(mcDaysOld != -1)
  4349. {
  4350. result += '<i>Expires ' + uroUtils.ParseDaysToGo(mcDaysOld) + ' ';
  4351. result += '(' + uroUtils.GetDateTimeString(mcSubmittedTS) +')</i><br>';
  4352. }
  4353.  
  4354. let mcHasMyComments = false;
  4355. let mcNComments = moAttrs.conversation.length;
  4356. if(mcNComments > 0)
  4357. {
  4358. for(let j=0; j<mcNComments; j++)
  4359. {
  4360. if(moAttrs.conversation[j].userID == uroUserID)
  4361. {
  4362. mcHasMyComments = true;
  4363. break;
  4364. }
  4365. }
  4366. }
  4367. result += '<br>' + mcNComments +' comment';
  4368. if(mcNComments != 1) result += 's';
  4369. if((mcHasMyComments === false) && (mcNComments > 0)) result += ' (none by me)';
  4370.  
  4371. // add "ignore for this session" link
  4372. result += '<br><a href="#" id="_addtoignore">Hide for this session</a>';
  4373. uroPopup.hasIgnoreLink = true;
  4374.  
  4375. uroPopup.newPopupType = 'map_comment';
  4376. uroPopup.isMapComment = true;
  4377.  
  4378. uroPopup.result += result;
  4379. uroPopup.Show();
  4380. }
  4381. }
  4382. else
  4383. {
  4384. let mcOtherID = moAttrs.id;
  4385. uroDBG.AddLog('map comment '+mcOtherID+' is also highlighted');
  4386. }
  4387. }
  4388. else
  4389. {
  4390. uroDBG.AddLog('map comment '+uroFID+' has renderIntent==highlight but is offscreen... blocking popup');
  4391. }
  4392. }
  4393. }
  4394. }
  4395. },
  4396. AddClosureRow: function(rcObj)
  4397. {
  4398. let startDate = rcObj.attributes.startDate;
  4399. let endDate = "unknown";
  4400. if(rcObj.attributes.endDate !== null)
  4401. {
  4402. endDate = rcObj.attributes.endDate;
  4403. }
  4404. let provider = "---";
  4405. if(rcObj.attributes.provider !== null)
  4406. {
  4407. provider = rcObj.attributes.provider;
  4408. }
  4409. else if(rcObj.attributes.createdBy !== null)
  4410. {
  4411. provider = uroUtils.GetUserNameAndRank(rcObj.attributes.createdBy);
  4412. }
  4413. let reason = "---";
  4414. if(rcObj.attributes.reason !== null)
  4415. {
  4416. reason = rcObj.attributes.reason;
  4417. }
  4418. let mte = "---";
  4419. if(rcObj.attributes.eventId !== null)
  4420. {
  4421. try
  4422. {
  4423. mte = W.model.majorTrafficEvents.objects[rcObj.attributes.eventId].attributes.names[0].value;
  4424. }
  4425. catch(err)
  4426. {
  4427. }
  4428. }
  4429. let startOffset = uroGetRTCOffset(rcObj.attributes.startDate);
  4430. let duration = uroGetRTCDuration(rcObj);
  4431. let durationStr = '';
  4432.  
  4433. if(duration > 1)
  4434. {
  4435. durationStr = I18n.lookup("datetime.distance_in_words.x_days").other;
  4436. durationStr = durationStr.replace("%{count}", duration);
  4437. }
  4438. else
  4439. {
  4440. durationStr = I18n.lookup("datetime.distance_in_words.x_days").one;
  4441. if(duration === 0)
  4442. {
  4443. durationStr = "<" + durationStr;
  4444. }
  4445. }
  4446. let state = uroGetRTCState(rcObj);
  4447. let status = uroGetRTCStateText(rcObj);
  4448. let bgCol = '';
  4449. if(state === uroEnums.SRTC.EXPIRED)
  4450. {
  4451. bgCol = '#A0A0A0';
  4452. }
  4453. else if(state === uroEnums.SRTC.ACTIVE)
  4454. {
  4455. bgCol = '#FFFFFF';
  4456. }
  4457. else if(state === uroEnums.SRTC.FUTURE)
  4458. {
  4459. // For future closures, override the default status text with an indication
  4460. // of how many days until the start of the closure
  4461. if(startOffset == 0)
  4462. {
  4463. status = I18n.lookup("date.today");
  4464. status = status.charAt(0).toUpperCase() + status.slice(1);
  4465. }
  4466. else if(startOffset == 1)
  4467. {
  4468. status = I18n.lookup("datetime.distance_in_words.x_days").one;
  4469. }
  4470. else
  4471. {
  4472. status = I18n.lookup("datetime.distance_in_words.x_days").other;
  4473. status = status.replace("%{count}", startOffset);
  4474. }
  4475. bgCol = '#C0C0C0';
  4476. }
  4477. let result = '';
  4478. result += '<tr bgcolor="' + bgCol + '">';
  4479. result += '<td nowrap>' + status + '</td>';
  4480. result += '<td nowrap>' + startDate + ' to ' + endDate + ' (' + durationStr + ')</td>';
  4481. result += '<td nowrap>' + provider + '</td>';
  4482. result += '<td nowrap>' + reason + '</td>';
  4483. result += '<td nowrap>' + mte + '</td>';
  4484. result += '</td></tr>';
  4485. return result;
  4486. },
  4487. AddClosureDetails: function(cTypes, addType, addDesc)
  4488. {
  4489. let retval = '';
  4490. if((cTypes & addType) === addType)
  4491. {
  4492. retval += '<tr><td colspan=4><b>' + addDesc + ':</b></td></tr>';
  4493. for(let closure in uroRTCObjs)
  4494. {
  4495. if(uroRTCObjs.hasOwnProperty(closure))
  4496. {
  4497. let cObj = uroRTCObjs[closure];
  4498. if(cObj.direction === addType)
  4499. {
  4500. retval += uroPopup.AddClosureRow(cObj);
  4501. }
  4502. }
  4503. }
  4504. }
  4505. return retval;
  4506. },
  4507. Segment: function()
  4508. {
  4509. if(uroUtils.GetCBChecked('_cbInhibitSegPopup') === false)
  4510. {
  4511. {
  4512. if(uroUtils.GetExtent().intersectsBounds(uroMarkers.obj.attributes.geometry.getBounds()))
  4513. {
  4514. let result = '';
  4515. let doPopUp = false;
  4516. let restObj;
  4517.  
  4518. if(uroMousedOverMapComment !== null)
  4519. {
  4520. uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for segment highlight');
  4521. uroMousedOverOtherObjectWithinMapComment = true;
  4522. }
  4523.  
  4524. let streetID = uroMarkers.obj.attributes.primaryStreetID;
  4525. if(streetID !== null)
  4526. {
  4527. // generic segment data
  4528. if(uroUtils.GetCBChecked('_cbInhibitSegGenericPopup') === false)
  4529. {
  4530. doPopUp = true;
  4531. uroDBG.AddLog('building popup for segment '+streetID);
  4532.  
  4533. let isToll = ((uroMarkers.obj.attributes.fwdToll == true) || (uroMarkers.obj.attributes.revToll == true));
  4534. result += uroGetAddress(streetID, null, true, false, isToll);
  4535.  
  4536. if(uroMarkers.obj.attributes.streetIDs.length > 0)
  4537. {
  4538. // list any alternate names
  4539. result += '<br>Alternate names:<br>';
  4540. for(let i = 0; i < uroMarkers.obj.attributes.streetIDs.length; ++i)
  4541. {
  4542. result += '&nbsp;<i>' + W.model.streets.objects[uroMarkers.obj.attributes.streetIDs[i]].attributes.name + ', ';
  4543. let cityName = "";
  4544. if(W.model.cities.objects[W.model.streets.objects[uroMarkers.obj.attributes.streetIDs[i]].attributes.cityID] != undefined)
  4545. {
  4546. cityName = W.model.cities.objects[W.model.streets.objects[uroMarkers.obj.attributes.streetIDs[i]].attributes.cityID].attributes.name;
  4547. }
  4548. if(cityName != "")
  4549. {
  4550. result += cityName;
  4551. }
  4552. else
  4553. {
  4554. result += ' no city';
  4555. }
  4556. result += '</i><br>';
  4557. }
  4558. result += '<br>';
  4559. }
  4560.  
  4561. result += '<b>ID: </b>'+uroMarkers.obj.attributes.id+' - ';
  4562. result += uroPopup.GetFormattedLocks(uroMarkers.obj.attributes);
  4563. result += ' - ';
  4564.  
  4565. let level = uroMarkers.obj.attributes.level;
  4566. result += '<b>' + I18n.lookup("edit.segment.fields.level") +': </b>';
  4567. if(level == 0)
  4568. {
  4569. result += I18n.lookup("edit.segment.levels")[0];
  4570. }
  4571. else
  4572. {
  4573. result += level;
  4574. }
  4575. result += '<br>';
  4576.  
  4577. let leBy = uroMarkers.obj.attributes.updatedBy;
  4578. let leOn = uroMarkers.obj.attributes.updatedOn;
  4579. if(leOn == null)
  4580. {
  4581. leBy = uroMarkers.obj.attributes.createdBy;
  4582. leOn = uroMarkers.obj.attributes.createdOn;
  4583. }
  4584. result += "<b>Last edit by</b> " + uroUtils.GetUserNameAndRank(leBy) + " <b>on</b> " + uroUtils.GetDateTimeString(leOn) + "<br><br>";
  4585.  
  4586. let fwdSpeed = uroMarkers.obj.attributes.fwdMaxSpeed;
  4587. let revSpeed = uroMarkers.obj.attributes.revMaxSpeed;
  4588. let fwdLanes = uroMarkers.obj.attributes.fwdLaneCount;
  4589. let revLanes = uroMarkers.obj.attributes.revLaneCount;
  4590. let fwdWidth = 'Not set';
  4591. let revWidth = 'Not set';
  4592. if(uroMarkers.obj.attributes.fromLanesInfo != null)
  4593. {
  4594. fwdWidth = uroGetLengthString(uroMarkers.obj.attributes.fromLanesInfo.laneWidth);
  4595. }
  4596. if(uroMarkers.obj.attributes.toLanesInfo != null)
  4597. {
  4598. revWidth = uroGetLengthString(uroMarkers.obj.attributes.toLanesInfo.laneWidth);
  4599. }
  4600. let fwdUnverified = uroMarkers.obj.attributes.fwdMaxSpeedUnverified;
  4601. let revUnverified = uroMarkers.obj.attributes.revMaxSpeedUnverified;
  4602. let fwdASC = ((uroMarkers.obj.attributes.fwdFlags & 1) === 1);
  4603. let revASC = ((uroMarkers.obj.attributes.revFlags & 1) === 1);
  4604. let roadType = uroMarkers.obj.attributes.roadType;
  4605. let verifyLimits = true;
  4606. if((roadType === 17) || (roadType === 20))
  4607. {
  4608. verifyLimits = false;
  4609. }
  4610.  
  4611. result += '<table border=1><tr><th>Dir</th><th>Speed</th><th>ASC</th><th>Lanes</th><th>Width</th></tr>';
  4612. if(uroMarkers.obj.attributes.fwdDirection)
  4613. {
  4614. result += '<tr><td><b>A-B</b></td><td>'+uroUtils.GetLocalisedSpeedString(fwdSpeed)+'</td><td>';
  4615. if(fwdASC == true)
  4616. {
  4617. result += 'Yes';
  4618. }
  4619. else
  4620. {
  4621. result += 'No';
  4622. }
  4623. result += '</td><td>';
  4624. if(fwdLanes > 0)
  4625. {
  4626. result += fwdLanes;
  4627. }
  4628. else
  4629. {
  4630. result += 'Not set';
  4631. }
  4632. result += '</td><td>'+fwdWidth+'</td></tr>';
  4633. }
  4634. if(uroMarkers.obj.attributes.revDirection)
  4635. {
  4636. result += '<tr><td><b>B-A</b></td><td>'+uroUtils.GetLocalisedSpeedString(revSpeed)+'</td><td>';
  4637. if(revASC == true)
  4638. {
  4639. result += 'Yes';
  4640. }
  4641. else
  4642. {
  4643. result += 'No';
  4644. }
  4645. result += '</td><td>';
  4646. if(revLanes > 0)
  4647. {
  4648. result += revLanes;
  4649. }
  4650. else
  4651. {
  4652. result += 'Not set';
  4653. }
  4654. result += '</td><td>'+revWidth+'</td></tr>';
  4655. }
  4656. result += '</table>';
  4657.  
  4658. if((uroMarkers.obj.attributes.fwdDirection) && (uroMarkers.obj.attributes.revDirection) && (fwdSpeed != revSpeed) && (!fwdUnverified) && (!revUnverified))
  4659. {
  4660. result += '<br>Two-way segment has different verified speed limits...';
  4661. }
  4662. }
  4663.  
  4664. // segment restrictions
  4665. if(uroMarkers.obj.attributes.restrictions.length > 0)
  4666. {
  4667. result += '<br><table border=1>';
  4668. doPopUp = true;
  4669. let fwdResult = '<tr><td colspan=13><b>A-B restrictions:</b></td></tr>';
  4670. let revResult = '<tr><td colspan=13><b>B-A restrictions:</b></td></tr>';
  4671. let bothResult = '<tr><td colspan=13><b>Two-way restrictions:</b></td></tr>';
  4672.  
  4673. let nABRestrictions = 0;
  4674. let nBARestrictions = 0;
  4675. let nBothRestrictions = 0;
  4676. for(let idx = 0; idx < uroMarkers.obj.attributes.restrictions.length; idx++)
  4677. {
  4678. restObj = uroMarkers.obj.attributes.restrictions[idx];
  4679. if(restObj._direction === "FWD")
  4680. {
  4681. nABRestrictions++;
  4682. fwdResult += uroFormatRestriction(restObj);
  4683. }
  4684. else if(restObj._direction === "REV")
  4685. {
  4686. nBARestrictions++;
  4687. revResult += uroFormatRestriction(restObj);
  4688. }
  4689. else if(restObj._direction === "BOTH")
  4690. {
  4691. nBothRestrictions++;
  4692. bothResult += uroFormatRestriction(restObj);
  4693. }
  4694. else
  4695. {
  4696. uroDBG.AddLog("unknown restriction direction...");
  4697. }
  4698. }
  4699. if(nABRestrictions > 0)
  4700. {
  4701. result += fwdResult;
  4702. }
  4703. if(nBARestrictions > 0)
  4704. {
  4705. result += revResult;
  4706. }
  4707. if(nBothRestrictions > 0)
  4708. {
  4709. result += bothResult;
  4710. }
  4711. result += '</table>';
  4712. }
  4713. if(uroLayers.layers[uroLayers.ID.RTC].l.getVisibility() === true)
  4714. {
  4715. let closureTypes = uroGetSelectedSegmentRTCs(uroMarkers.obj.attributes.id);
  4716. if(closureTypes !== uroEnums.DRTC.NONE)
  4717. {
  4718. result += '<br><table border=1 width="100%">';
  4719.  
  4720. result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.SEG_AB, "A-B closures");
  4721. result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.SEG_BA, "B-A closures");
  4722. result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.SEG_BI, "Two-way closures");
  4723. result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.TURN_OUT, "Outbound turn closures");
  4724. result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.TURN_IN, "Inbound turn closures");
  4725. doPopUp = true;
  4726. result += '</table>';
  4727. }
  4728. }
  4729.  
  4730. if(doPopUp === true)
  4731. {
  4732. uroFID = uroMarkers.obj.attributes.id;
  4733. uroPopup.newPopupType = 'segment_restriction';
  4734. }
  4735. }
  4736. uroPopup.result += result;
  4737. uroPopup.Show();
  4738. }
  4739. else
  4740. {
  4741. uroDBG.AddLog('segment '+uroFID+' has renderIntent==highlight but is offscreen... blocking popup');
  4742. }
  4743. }
  4744. }
  4745. },
  4746. Node: function()
  4747. {
  4748. if(uroUtils.GetCBChecked('_cbInhibitNodesPopup') === false)
  4749. {
  4750. let result = '';
  4751. let ureq = W.model.nodes.objects[uroMarkers.id];
  4752. if(ureq === undefined)
  4753. {
  4754. uroMarkers.id = null;
  4755. }
  4756. else
  4757. {
  4758. if(uroMousedOverMapComment !== null)
  4759. {
  4760. uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for node highlight');
  4761. uroMousedOverOtherObjectWithinMapComment = true;
  4762. }
  4763. uroPopup.newPopupType = 'node';
  4764. uroFID = uroMarkers.id;
  4765. uroDBG.AddLog('building popup for node '+uroFID);
  4766. result += '<b>Node: ' + uroFID + '</b><br>';
  4767.  
  4768. let nodeSegRTCs = [];
  4769. for(let k=0; k<ureq.attributes.segIDs.length; k++)
  4770. {
  4771. let nodeSegID = ureq.attributes.segIDs[k];
  4772. let nodeSegObj = W.model.segments.objects[nodeSegID];
  4773. if(nodeSegObj !== undefined)
  4774. {
  4775. let nodeStreetID = nodeSegObj.attributes.primaryStreetID;
  4776. result += uroGetAddress(nodeStreetID, null, false, true, false);
  4777. }
  4778. uroGetSelectedSegmentRTCs(nodeSegID);
  4779. nodeSegRTCs = nodeSegRTCs.concat(uroRTCObjs);
  4780. }
  4781. }
  4782. uroPopup.result += result;
  4783. uroPopup.Show();
  4784. }
  4785. },
  4786. UMPExtras: function(ureq)
  4787. {
  4788. // add "open new WME tab" link
  4789. let urPos=new OpenLayers.LonLat();
  4790. if(uroPopup.isPlaceUpdate)
  4791. {
  4792. urPos.lon = ureq.getOLGeometry().getCentroid().x;
  4793. urPos.lat = ureq.getOLGeometry().getCentroid().y;
  4794. }
  4795. else
  4796. {
  4797. if(ureq.attributes.geometry.realX === undefined)
  4798. {
  4799. urPos.lon = ureq.attributes.geometry.x;
  4800. urPos.lat = ureq.attributes.geometry.y;
  4801. }
  4802. else
  4803. {
  4804. urPos.lon = ureq.attributes.geometry.realX;
  4805. urPos.lat = ureq.attributes.geometry.realY;
  4806. }
  4807. }
  4808. urPos = uroUtils.ConvertMercatorToWGS84(urPos);
  4809. let urLink = document.location.href;
  4810. let urLayers = '';
  4811. urLink = urLink.substr(0,urLink.indexOf('?zoomLevel'));
  4812. urLink += '?zoomLevel=17&lat='+urPos.lat+'&lon='+urPos.lon+urLayers;
  4813.  
  4814. if(uroPopup.isUR) urLink += '&mapUpdateRequest='+uroMarkers.id;
  4815. else if(uroPopup.isTurnProb) urLink += '&showturn='+uroMarkers.id+'&endshow';
  4816. else if(uroPopup.isProblem) urLink += '&mapProblem='+uroMarkers.id;
  4817. else if(uroPopup.isPlaceUpdate)
  4818. {
  4819. if(uroMarkers.type == uroLayers.ID.PUR)
  4820. {
  4821. urLink += '&showpur='+uroMarkers.id+'&endshow';
  4822. }
  4823. else
  4824. {
  4825. urLink += '&showppur='+uroMarkers.id+'&endshow';
  4826. }
  4827. }
  4828.  
  4829. let targetTab = "_uroTab_" + Math.round(Math.random()*1000000);
  4830. uroPopup.result += '<hr><ul><li><a href="'+urLink+'" id="_openInNewTab" target="'+targetTab+'">Open in new tab</a> - ';
  4831. uroPopup.hasOpenInNewTabLink = true;
  4832. uroPopup.result += '<a href="#" id="_recentreSession">centre in current tab</a>';
  4833. uroPopup.hasRecentreSessionLink = true;
  4834.  
  4835. // add "open new livemap tab" link
  4836. let lmLink = null;
  4837. if(document.getElementById("livemap-link") != null)
  4838. {
  4839. uroDBG.AddLog('Livemap link in livemap-link id element');
  4840. lmLink = document.getElementById("livemap-link").href;
  4841. }
  4842. else if(document.getElementsByClassName("livemap-link") != null)
  4843. {
  4844. uroDBG.AddLog('Livemap link in livemap-link class element');
  4845. lmLink = document.getElementsByClassName("livemap-link")[0].href;
  4846. }
  4847. else
  4848. {
  4849. uroDBG.AddLog('Livemap link not found...');
  4850. }
  4851. if(lmLink !== null)
  4852. {
  4853. let zpos = lmLink.indexOf('?');
  4854. if(zpos > -1) lmLink = lmLink.substr(0,zpos);
  4855. lmLink += '?zoom=17&lat='+urPos.lat+'&lon='+urPos.lon+'&layers=BTTTT';
  4856. uroPopup.result += '<li><a href="'+lmLink+'" target="_lmTab">Open in new livemap tab</a>';
  4857. }
  4858. if(!uroPopup.isPlaceUpdate)
  4859. {
  4860. // add "ignore for this session" link
  4861. uroPopup.result += '<li><a href="#" id="_addtoignore">Hide for this session</a></ul>';
  4862. uroPopup.hasIgnoreLink = true;
  4863. }
  4864.  
  4865. uroPopup.Show();
  4866. },
  4867. Preamble: function()
  4868. {
  4869. let retval = true;
  4870.  
  4871. if
  4872. (
  4873. (uroMarkers.type === null) &&
  4874. (uroPopup.timer === 0)
  4875. )
  4876. {
  4877. if(uroPopup.shown === true)
  4878. {
  4879. uroPopup.Hide();
  4880. }
  4881. uroMousedOverMapComment = null;
  4882. retval = false;
  4883. }
  4884. if(retval === true)
  4885. {
  4886. if(uroMTEMode)
  4887. {
  4888. retval = false;
  4889. }
  4890. }
  4891.  
  4892. if(retval === true)
  4893. {
  4894. if(!uroInit.initialised)
  4895. {
  4896. retval = false;
  4897. }
  4898. }
  4899.  
  4900. if(retval === true)
  4901. {
  4902. if((uroMouseIsDown) && (uroMarkers.mouseButtons === 0))
  4903. {
  4904. uroDBG.AddLog('trapped erroneous mousedown state');
  4905. uroMouseIsDown = false;
  4906. }
  4907. if(uroMouseIsDown)
  4908. {
  4909. retval = false;
  4910. }
  4911. }
  4912.  
  4913. if(retval === true)
  4914. {
  4915. if(OpenLayers === null)
  4916. {
  4917. if(uroNullOpenLayers === false)
  4918. {
  4919. uroDBG.AddLog('caught null OpenLayers');
  4920. uroNullOpenLayers = true;
  4921. }
  4922. retval = false;
  4923. }
  4924. else
  4925. {
  4926. uroNullOpenLayers = false;
  4927. }
  4928. }
  4929.  
  4930. if(retval === true)
  4931. {
  4932. if(uroLayers.layers[uroLayers.ID.UR].l === null)
  4933. {
  4934. if(uroNullURLayer === false)
  4935. {
  4936. uroDBG.AddLog('caught null UR layer');
  4937. uroNullURLayer = true;
  4938. }
  4939. retval = false;
  4940. }
  4941. else
  4942. {
  4943. uroNullURLayer = false;
  4944. }
  4945. }
  4946.  
  4947. if(retval === true)
  4948. {
  4949. if(uroLayers.layers[uroLayers.ID.MP].l === null)
  4950. {
  4951. if(uroNullProblemLayer === false)
  4952. {
  4953. uroDBG.AddLog('caught null problem layer');
  4954. uroNullProblemLayer = true;
  4955. }
  4956. retval = false;
  4957. }
  4958. else
  4959. {
  4960. uroNullProblemLayer = false;
  4961. }
  4962. }
  4963.  
  4964. if(retval === true)
  4965. {
  4966. if(uroUtils.GetCBChecked('_cbMasterEnable') === false)
  4967. {
  4968. retval = false;
  4969. }
  4970. }
  4971.  
  4972. if(retval === true)
  4973. {
  4974. if(uroTestPointerOutsideMap(uroMarkers.clientX, uroMarkers.clientY))
  4975. {
  4976. retval = false;
  4977. }
  4978. }
  4979.  
  4980.  
  4981. if(retval === true)
  4982. {
  4983. uroPopup.mouseX = uroMarkers.mouseX;
  4984. uroPopup.mouseY = uroMarkers.mouseY;
  4985. uroPopup.result = '';
  4986. uroPopup.hasIgnoreLink = false;
  4987. uroPopup.hasDeleteLink = false;
  4988. uroPopup.hasAddWatchLink = false;
  4989. uroPopup.hasRemoveWatchLink = false;
  4990. uroPopup.hasUpdateWatchLink = false;
  4991. uroPopup.hasRecentreSessionLink = false;
  4992. uroPopup.hasOpenInNewTabLink = false;
  4993. uroPopup.isVenue = false;
  4994. uroPopup.isMapComment = false;
  4995. uroPopup.newPopupType = null;
  4996. uroPopup.ureqID = null;
  4997. uroPopup.isUR = false;
  4998. uroPopup.isProblem = false;
  4999. uroPopup.isTurnProb = false;
  5000. uroPopup.isPlaceUpdate = false;
  5001. uroPopup.renderIntent = uroGetFeatureRenderIntent(uroMarkers.obj);
  5002.  
  5003. uroInit.WazeBits();
  5004.  
  5005. let mouseLonLat = W.map.getLonLatFromViewPortPx(new OpenLayers.Pixel(uroPopup.mouseX, uroPopup.mouseY));
  5006. let mousePoint = new OpenLayers.Geometry.Point(mouseLonLat.lon, mouseLonLat.lat);
  5007. if(uroMousedOverMapComment !== null)
  5008. {
  5009. if(W.model.mapComments.objects[uroMousedOverMapComment] === undefined)
  5010. {
  5011. uroDBG.AddLog('clearing uroMousedOverMapComment: object no longer exists in current map view');
  5012. uroMousedOverMapComment = null;
  5013. }
  5014. else if(W.model.mapComments.objects[uroMousedOverMapComment].attributes.geometry.containsPoint(mousePoint) === false)
  5015. {
  5016. uroDBG.AddLog('clearing uroMousedOverMapComment: pointer no longer within comment boundary');
  5017. uroMousedOverMapComment = null;
  5018. }
  5019. }
  5020. let popupXOffset = document.getElementById('editor-container').getBoundingClientRect().x;
  5021. let popupYOffset = $(document.getElementById("WazeMap")).offset().top;
  5022. uroPopup.pX = uroPopup.mouseX + popupXOffset + 10;
  5023. uroPopup.pY = uroPopup.mouseY + popupYOffset;
  5024. }
  5025.  
  5026. return retval;
  5027. },
  5028. Show: function()
  5029. {
  5030. if(uroPopup.suppressed === false)
  5031. {
  5032. if((uroFID != uroShownFID) || (uroPopup.newPopupType != uroPopup.shownType))
  5033. {
  5034. if(uroFID != uroShownFID) uroDBG.AddLog('FID mismatch, show popup: '+uroFID+'/'+uroShownFID);
  5035. else uroDBG.AddLog('Popup type mismatch: '+uroPopup.newPopupType+'/'+uroPopup.shownType);
  5036. uroShownFID = uroFID;
  5037. uroPopup.shownType = uroPopup.newPopupType;
  5038. uroPopup.shown = false;
  5039. }
  5040. if(uroPopup.shown === false)
  5041. {
  5042. uroPopup.shown = true;
  5043. uroDiv.style.height = "auto";
  5044. uroDiv.style.width = "auto";
  5045. uroDiv.style.overflow = "auto";
  5046. uroDiv.innerHTML = uroUtils.ModifyHTML(uroPopup.result);
  5047. if((uroFID != -1) && (uroPopup.hasIgnoreLink === true))
  5048. {
  5049. uroUtils.AddEventListener('_addtoignore','click', uroIgnore.Add, true);
  5050. }
  5051. if(uroPopup.hasDeleteLink === true)
  5052. {
  5053. uroUtils.AddEventListener('_deleteobject','click', uroDeleteObject, true);
  5054. }
  5055. if(uroPopup.hasRemoveWatchLink === true)
  5056. {
  5057. uroUtils.AddEventListener('_removefromwatchlist','click', uroOWL.RemoveCamFromWatchList, true);
  5058. }
  5059. if(uroPopup.hasAddWatchLink === true)
  5060. {
  5061. uroUtils.AddEventListener('_addtowatchlist','click', uroOWL.AddCamToWatchList, true);
  5062. }
  5063. if(uroPopup.hasUpdateWatchLink === true)
  5064. {
  5065. uroUtils.AddEventListener('_updatewatchlist','click', uroOWL.UpdateCamWatchList, true);
  5066. }
  5067. if(uroPopup.hasOpenInNewTabLink === true)
  5068. {
  5069. uroUtils.AddEventListener('_openInNewTab','mouseup', uroOpenNewTab, true);
  5070. }
  5071. if(uroPopup.hasRecentreSessionLink === true)
  5072. {
  5073. if(uroPopup.isUR) uroUtils.AddEventListener('_recentreSession', 'click', uroRecentreSessionOnUR, true);
  5074. else if((uroPopup.isProblem)||(uroPopup.isTurnProb)) uroUtils.AddEventListener('_recentreSession', 'click', uroRecentreSessionOnMP, true);
  5075. else if(uroPopup.isPlaceUpdate)
  5076. {
  5077. if(uroPopup.newPopupType == uroLayers.ID.PUR)
  5078. {
  5079. uroUtils.AddEventListener('_recentreSession', 'click', uroRecentreSessionOnPUR, true);
  5080. }
  5081. else
  5082. {
  5083. uroUtils.AddEventListener('_recentreSession', 'click', uroRecentreSessionOnPPUR, true);
  5084. }
  5085. }
  5086. else if(uroPopup.isVenue) uroUtils.AddEventListener('_recentreSession', 'click', uroRecentreSessionOnVenueNavPoint, true);
  5087. }
  5088. // restrict the popup width to be no wider than just under half the window width to avoid it
  5089. // completely overlapping the marker it's associated with - by keeping it to just below half
  5090. // the window width we guarantee that it'll fit either to the left or the right of the marker
  5091. // no matter how far across the screen the marker is located...
  5092. let rw = parseInt(uroDiv.clientWidth);
  5093. if(rw > (window.innerWidth * 0.45))
  5094. {
  5095. rw = (window.innerWidth * 0.45);
  5096. uroDiv.style.width = rw+'px';
  5097. }
  5098. // get the div height after any adjustment of the width above, to account for whatever content
  5099. // reflow may have occurred as a result of reducing the width...
  5100. let rh = parseInt(uroDiv.clientHeight);
  5101. // similarly restrict the popup height to avoid it dropping of the bottom of the screen if a
  5102. // segment has a bunch of closures/restrictions
  5103. rh = parseInt(uroDiv.clientHeight);
  5104. if(rh > (window.innerHeight * 0.80))
  5105. {
  5106. rh = (window.innerHeight * 0.80);
  5107. uroDiv.style.height = rh+'px';
  5108. uroDiv.style.overflow = 'scroll';
  5109. }
  5110. let origPopupX = uroPopup.pX;
  5111. let movedLeft = false;
  5112. if((uroPopup.pX + rw) > window.innerWidth)
  5113. {
  5114. // where the popup would be off the right hand side of the screen, move it completely over to the
  5115. // other side of the mouse pointer
  5116. uroPopup.pX -= (rw + 20);
  5117. if(uroPopup.pX < 0) uroPopup.pX = 0;
  5118. movedLeft = true;
  5119. }
  5120. if((uroPopup.pY + rh) > window.innerHeight)
  5121. {
  5122. // where the popup would be off the bottom of the screen, shift it up just far enough to be
  5123. // fully visible
  5124. uroPopup.pY -= (((uroPopup.pY + rh) - window.innerHeight) + 30);
  5125. }
  5126. if(uroPopup.pY < 0) uroPopup.pY = 0;
  5127. uroDiv.style.top = uroPopup.pY+'px';
  5128. uroDiv.style.left = uroPopup.pX+'px';
  5129. if(movedLeft === true)
  5130. {
  5131. // after relocating the popup to the left of the pointer, it may end up resizing itself
  5132. // which may then cause it to completely overlap the UR marker, so perform one more check
  5133. // of the div width and nudge to the left if required...
  5134. rw = parseInt(uroDiv.clientWidth);
  5135. if(rw > (window.innerWidth * 0.45))
  5136. {
  5137. rw = (window.innerWidth * 0.45);
  5138. uroDiv.style.width = rw+'px';
  5139. }
  5140. let nudgeDist = parseInt(20 + (uroPopup.pX + rw) - origPopupX);
  5141. if((uroPopup.pX + rw + 30) >= origPopupX)
  5142. {
  5143. uroDiv.style.left = parseInt(uroPopup.pX - nudgeDist)+'px';
  5144. }
  5145. }
  5146.  
  5147. uroDBG.AddLog('display popup at '+uroPopup.pX+','+uroPopup.pY);
  5148. uroDiv.style.visibility = 'visible';
  5149. uroPopup.autoHideTimer = (uroUtils.GetElmValue('_inputPopupAutoHideTimeout') * 10);
  5150. }
  5151. uroPopup.timer = -1;
  5152. }
  5153. },
  5154. Generate: function()
  5155. {
  5156. if(uroPopup.Preamble() === true)
  5157. {
  5158. if(uroMarkers.type === uroLayers.ID.UR) uroPopup.UR();
  5159. else if(uroMarkers.type === uroLayers.ID.MP) uroPopup.MP();
  5160. else if((uroMarkers.type === uroLayers.ID.PUR)||(uroMarkers.type === uroLayers.ID.PPUR)||(uroMarkers.type === uroLayers.ID.RPUR)) uroPopup.PUR();
  5161. else if(uroMarkers.type === "venue") uroPopup.Venue();
  5162. else if(uroMarkers.type === "cam") uroPopup.Camera();
  5163. else if(uroMarkers.type === "comment") uroPopup.Comment();
  5164. else if(uroMarkers.type === "segment") uroPopup.Segment();
  5165. else if(uroMarkers.type === "node") uroPopup.Node();
  5166. else
  5167. {
  5168. uroDBG.AddLog("request to generate popup for unknown type - " + uroMarkers.type);
  5169. }
  5170. }
  5171. },
  5172. Hide: function()
  5173. {
  5174. if(uroPopup.shown)
  5175. {
  5176. uroDiv.style.visibility = 'hidden';
  5177. uroPopup.shown = false;
  5178. uroPopup.timer = -2;
  5179. uroShownFID = -1;
  5180. }
  5181. uroPopup.suppressed = false;
  5182. },
  5183. Suppress: function()
  5184. {
  5185. uroDiv.style.visibility = 'hidden';
  5186. window.getSelection().removeAllRanges();
  5187. uroPopup.suppressed = true;
  5188. },
  5189. MouseOver: function()
  5190. {
  5191. uroPopup.mouseIn = true;
  5192. },
  5193. MouseOut: function()
  5194. {
  5195. uroPopup.mouseIn = false;
  5196. }
  5197. };
  5198. const uroURExtras = // UR marker enhancements
  5199. {
  5200. urList : [],
  5201. URListEntry: function(id, customType, hasMyComments, nComments, ageLastComment)
  5202. {
  5203. this.id = id;
  5204. this.customType = customType;
  5205. this.hasMyComments = hasMyComments;
  5206. this.nComments = nComments;
  5207. this.ageLastComment = ageLastComment;
  5208. },
  5209. AddToList: function(urID, customType, hasMyComments, nComments, ageLastComment)
  5210. {
  5211. uroURExtras.urList.push(new uroURExtras.URListEntry(urID, customType, hasMyComments, nComments, ageLastComment));
  5212. },
  5213. AddCommentCounts: function()
  5214. {
  5215. // Remove existing count elements before (re-)rendering, as WME doesn't automatically do this
  5216. // for us now that the way it handles the marker layers has changed...
  5217. let toRemove = document.querySelectorAll('#uroCommentCount');
  5218. let trCount = toRemove.length;
  5219. while(trCount > 0)
  5220. {
  5221. --trCount;
  5222. toRemove[trCount].remove();
  5223. }
  5224. let addCommentCount = false;
  5225. let nURs = uroURExtras.urList.length;
  5226.  
  5227. if(uroUtils.GetCBChecked('_cbMasterEnable') === true)
  5228. {
  5229. addCommentCount = ((nURs > 0) && (uroUtils.GetCBChecked('_cbCommentCount') === true));
  5230. }
  5231.  
  5232. if(addCommentCount === true)
  5233. {
  5234. for(let i = 0; i < nURs; ++i)
  5235. {
  5236. let uObj = uroURExtras.urList[i];
  5237. let nComments = uObj.nComments;
  5238. let marker = uroGetMarker(uroLayers.ID.UR, uObj.id);
  5239. if((marker !== null) && (nComments !== 0))
  5240. {
  5241. let mx = (marker.x.baseVal.value - 15);
  5242. let my = (marker.y.baseVal.value + 35);
  5243. let newSpan = '<foreignObject id="uroCommentCount" x="' + mx + '" y="' + my + '" width="100%" height="100%" style="pointer-events: none;">';
  5244.  
  5245. newSpan += '<div style="position:absolute;';
  5246. if(uObj.hasMyComments === true)
  5247. {
  5248. newSpan += 'background-color: yellow;';
  5249. }
  5250. else
  5251. {
  5252. newSpan += 'background-color: white;';
  5253. }
  5254. newSpan += 'font-size: 14px;font-weight: 800;';
  5255. newSpan += 'border-width: 1px;border-style: solid;border-radius: 12px;padding-left: 4px;padding-right: 4px;z-index: 1;';
  5256. newSpan += '">';
  5257. newSpan += nComments + 'c ';
  5258. newSpan += uObj.ageLastComment + 'd';
  5259. newSpan += '</div>';
  5260.  
  5261. newSpan += '</foreignObject>';
  5262. marker.insertAdjacentHTML("afterend", newSpan);
  5263. }
  5264. }
  5265. }
  5266. }
  5267. };
  5268. const uroInit = // script initialisation
  5269. {
  5270. initialised : false,
  5271. setupListeners : true,
  5272. finalisingListenerSetup : false,
  5273.  
  5274. WazeBitsAvailable: function()
  5275. {
  5276. uroDBG.AddLog('All WazeBits present and correct...');
  5277. W.prefs.on('change:isImperial', uroInit.Initialise);
  5278. uroLayers.Init();
  5279. // To avoid creating multiple URO tabs in the event that the initialise code
  5280. // gets called more than once, we now test for the presence of our tab and skip
  5281. // if it's already present
  5282. if(document.getElementById('uroTabHeader') === null)
  5283. {
  5284. uroInit.SetupUI();
  5285. }
  5286. },
  5287. WaitForW: function()
  5288. {
  5289. if(document.getElementsByClassName("sandbox").length > 0)
  5290. {
  5291. uroDBG.AddLog('WME practice mode detected, script is disabled...');
  5292. return;
  5293. }
  5294. if(window.W === undefined)
  5295. {
  5296. window.setTimeout(uroInit.WaitForW, 100);
  5297. return;
  5298. }
  5299. if (W.userscripts?.state?.isReady)
  5300. {
  5301. uroInit.WazeBitsAvailable();
  5302. }
  5303. else
  5304. {
  5305. document.addEventListener("wme-ready", uroInit.WazeBitsAvailable, {once: true});
  5306. }
  5307. },
  5308. AddInterceptor: function()
  5309. {
  5310. uroDBG.AddLog('Adding interceptor functions...');
  5311. // add interceptor function for window.confirm(), to inhibit the closure deletion confirmation that would
  5312. // pop up for each individual closure when we're using the delete all button - the user has already
  5313. // confirmed the delete action using our own requester
  5314. let _confirm = window.confirm;
  5315. window.confirm = function(msg)
  5316. {
  5317. let cm_delete_confirm = I18n.lookup("closures.delete_confirm").split('"')[0].trimRight(1);
  5318. if(msg.indexOf(cm_delete_confirm) != -1)
  5319. {
  5320. uroDBG.AddLog('intercepted closure delete confirmation...');
  5321. if(uroRTCClone.ConfirmDelete)
  5322. {
  5323. return _confirm(msg);
  5324. }
  5325. else
  5326. {
  5327. return true;
  5328. }
  5329. }
  5330. else if(typeof(msg) == 'undefined')
  5331. {
  5332. uroDBG.AddLog('Intercepted blank confirmation...');
  5333. return true;
  5334. }
  5335. else
  5336. {
  5337. return _confirm(msg);
  5338. }
  5339. };
  5340. uroConfirmIntercepted = true;
  5341. },
  5342. Initialise: function()
  5343. {
  5344. uroInit.initialised = false;
  5345. uroInit.setupListeners = true;
  5346. uroInit.finalisingListenerSetup = false;
  5347. uroInit.WaitForW();
  5348. },
  5349. WaitForControlsContainer: function()
  5350. {
  5351. if(document.getElementById('uroControlsContainer') === null)
  5352. {
  5353. window.setTimeout(uroInit.WaitForControlsContainer,500);
  5354. }
  5355. else
  5356. {
  5357. let updateURL = 'https://greasyfork.org/scripts/1952-uroverview-plus-uro';
  5358. uroDBG.AddLog('adding controls to sidebar container...');
  5359. let tabbyHTML = '<div id="uroTabHeader"><b><a href="'+updateURL+'" target="_blank">UROverview Plus</a></b> <label id="_uroVersion">'+uroRelease.version+'</label>';
  5360. tabbyHTML += '<label id="_uroDebugMode">(dbg)</label>';
  5361. tabbyHTML += '&nbsp;<input type="checkbox" id="_cbMasterEnable" checked>Enabled</input>';
  5362. tabbyHTML += uroTabs.CreateTabHeaders();
  5363. tabbyHTML += '</div>';
  5364. document.getElementById('uroControlsContainer').innerHTML = uroUtils.ModifyHTML(tabbyHTML);
  5365. uroTabs.CreateTabBodies();
  5366. // other sidebar elements
  5367. uroAMList = document.createElement('div');
  5368. uroAMList.style.color = "#ffff00";
  5369. uroCtrlHides = document.createElement('div');
  5370. // Object watchlist tab
  5371. {
  5372. uroOWL.CWLGroups = [];
  5373. uroTabs.PopulateOWL();
  5374. }
  5375. // footer for tabs container
  5376. uroCtrlHides.id = 'uroCtrlHides';
  5377. let tHTML = '<input type="button" id="_btnUndoLastHide" value="Undo last hide" />&nbsp;&nbsp;&nbsp;';
  5378. tHTML += '<input type="button" id="_btnClearSessionHides" value="Undo all hides" /><p>';
  5379. uroCtrlHides.innerHTML = uroUtils.ModifyHTML(tHTML);
  5380. // footer for AM list
  5381. uroAMList.id = 'uroAMList';
  5382. uroTabs.CtrlTabs[0][uroTabs.FIELDS.SHOWFN]();
  5383. window.addEventListener("beforeunload", uroConfig.SaveSettings, false);
  5384. }
  5385. },
  5386. AddTab_API: async function()
  5387. {
  5388. let {tabLabel, tabPane} = W.userscripts.registerSidebarTab("URO+");
  5389. tabLabel.innerText = "URO+";
  5390. tabPane.innerHTML = uroUtils.ModifyHTML(uroControls.innerHTML);
  5391. await W.userscripts.waitForElementConnected(tabPane);
  5392. uroInit.CompleteUISetup();
  5393. },
  5394. CompleteUISetup: function()
  5395. {
  5396. uroDBG.AddLog('waiting for controls container...');
  5397. uroInit.WaitForControlsContainer();
  5398. uroGetProblemTypes();
  5399. uroTabs.AddToDOM();
  5400. document.getElementById('uroControlsContainer').appendChild(uroCtrlHides);
  5401. uroInit.WazeBits();
  5402. uroDiv.addEventListener("mouseover", uroPopup.MouseOver, false);
  5403. uroDiv.addEventListener("mouseout", uroPopup.MouseOut, false);
  5404. if(sessionStorage.UROverview_FID_IgnoreList === undefined) sessionStorage.UROverview_FID_IgnoreList = '';
  5405. if(sessionStorage.UROverview_FID_WatchList === undefined) sessionStorage.UROverview_FID_WatchList = '';
  5406. if(uroConfirmIntercepted === false) uroInit.AddInterceptor();
  5407. uroUtils.AddStyle('urostyle_UnstackedMarkers', '.map-marker.marker-selected { transform: scale(1) !important; }');
  5408. uroMainTickHandlerID = window.setInterval(uroMainTick,1000);
  5409. },
  5410. SetupUI: function()
  5411. {
  5412. // create a new div to display the UR details floaty-box
  5413. uroDiv = document.createElement('div');
  5414. uroDiv.id = "uroDiv";
  5415. uroDiv.style.position = 'absolute';
  5416. uroDiv.style.visibility = 'hidden';
  5417. uroDiv.style.top = '0';
  5418. uroDiv.style.left = '0';
  5419. uroDiv.style.zIndex = 10000;
  5420. uroDiv.style.backgroundColor = 'aliceblue';
  5421. uroDiv.style.borderWidth = '3px';
  5422. uroDiv.style.borderStyle = 'solid';
  5423. uroDiv.style.borderRadius = '10px';
  5424. uroDiv.style.boxShadow = '5px 5px 10px Silver';
  5425. uroDiv.style.padding = '4px';
  5426. document.body.appendChild(uroDiv);
  5427. // create a new div to display script alerts
  5428. uroAlerts = document.createElement('div');
  5429. uroAlerts.id = "uroAlerts";
  5430. uroAlerts.style.position = 'fixed';
  5431. uroAlerts.style.visibility = 'hidden';
  5432. uroAlerts.style.top = '50%';
  5433. uroAlerts.style.left = '50%';
  5434. uroAlerts.style.zIndex = 10000;
  5435. uroAlerts.style.backgroundColor = 'aliceblue';
  5436. uroAlerts.style.borderWidth = '3px';
  5437. uroAlerts.style.borderStyle = 'solid';
  5438. uroAlerts.style.borderRadius = '10px';
  5439. uroAlerts.style.boxShadow = '5px 5px 10px Silver';
  5440. uroAlerts.style.padding = '4px';
  5441. uroAlerts.style.webkitTransform = "translate(-50%, -50%)";
  5442. uroAlerts.style.transform = "translate(-50%, -50%)";
  5443. let alertsHTML = '<div id="header" style="padding: 4px; background-color:LightGreen; font-weight: bold;">Alert title goes here...</div>';
  5444. alertsHTML += '<div id="content" style="padding: 4px; background-color:White; overflow:auto;max-height:500px">Alert content goes here...</div>';
  5445. alertsHTML += '<div id="controls" align="center" style="padding: 4px;">';
  5446. alertsHTML += '<span id="uroAlertTickBtn" style="cursor:pointer;font-size:14px;border:thin outset black;padding:2px 10px 2px 10px;">';
  5447. alertsHTML += '<i class="fa fa-check"> </i>';
  5448. alertsHTML += '<span id="uroAlertTickBtnCaption" style="font-weight: bold;"></span>';
  5449. alertsHTML += '</span>';
  5450. alertsHTML += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
  5451. alertsHTML += '<span id="uroAlertCrossBtn" style="cursor:pointer;font-size:14px;border:thin outset black;padding:2px 10px 2px 10px;">';
  5452. alertsHTML += '<i class="fa fa-times"> </i>';
  5453. alertsHTML += '<span id="uroAlertCrossBtnCaption" style="font-weight: bold;"></span>';
  5454. alertsHTML += '</span>';
  5455. alertsHTML += '</div>';
  5456. uroAlerts.innerHTML = uroUtils.ModifyHTML(alertsHTML);
  5457. document.body.appendChild(uroAlerts);
  5458. uroControls = document.createElement('section');
  5459. uroControls.style.fontSize = '12px';
  5460. uroControls.style.height = '100%';
  5461. uroControls.id = "sidepanel-uroverview";
  5462. uroControls.className = "tab-pane";
  5463. uroControls.innerHTML = uroUtils.ModifyHTML('<div id="uroControlsContainer" style="display:flex;flex-direction:column;height:80vh;"></div>');
  5464. uroInit.AddTab_API();
  5465. },
  5466. WazeBits: function()
  5467. {
  5468. uroMCLayer = null;
  5469.  
  5470. for(let i=0; i < W.map.layers.length; i++)
  5471. {
  5472. if(W.map.layers[i].name == 'mapComments') uroMCLayer = W.map.layers[i];
  5473. if(W.map.layers[i].name == 'venues') uroVenueLayer = W.map.layers[i];
  5474. }
  5475. },
  5476. FinalizeListenerSetup: function()
  5477. {
  5478. uroInit.finalisingListenerSetup = true;
  5479. // filter markers when the marker objects are modified (this happens whenever WME needs to load fresh marker data
  5480. // due to having panned/zoomed the map beyond the extents of the previously loaded data)
  5481. W.model.mapUpdateRequests.on("objectschanged", uroFilterURs_onObjectsChanged);
  5482. W.model.mapUpdateRequests.on("objectsadded", uroFilterURs_onObjectsAdded);
  5483. W.model.mapUpdateRequests.on("objectsremoved", uroFilterURs_onObjectsRemoved);
  5484. W.model.updateRequestSessions.on("objectsadded", uroUREvent_onObjectsAdded);
  5485. W.model.cameras.on("objectschanged", uroFilterCameras);
  5486. W.model.cameras.on("objectsadded", uroFilterCameras);
  5487. W.model.cameras.on("objectsremoved", uroFilterCameras);
  5488. W.model.mapProblems.on("objectschanged", uroFilterProblems);
  5489. W.model.mapProblems.on("objectsadded", uroFilterProblems);
  5490. W.model.mapProblems.on("objectsremoved", uroFilterProblems);
  5491. W.model.venues.on("objectschanged", uroFilterPlaces);
  5492. W.model.venues.on("objectsadded", uroFilterPlaces);
  5493. W.model.venues.on("objectsremoved", uroFilterPlaces);
  5494. W.model.mapComments.on("objectschanged", uroLayers.MCLayerChanged_changed);
  5495. W.model.mapComments.on("objectsadded", uroLayers.MCLayerChanged_added);
  5496. W.model.mapComments.on("objectsremoved", uroLayers.MCLayerChanged_removed);
  5497. uroMarkers.RegisterEvents();
  5498. uroLayers.InitialiseMOs();
  5499. uroUtils.AddEventListener('_btnUndoLastHide', "click", uroIgnore.RemoveLastAdded, true);
  5500. uroUtils.AddEventListener('_btnClearSessionHides', "click", uroIgnore.RemoveAll, true);
  5501. uroIgnore.EnableControls();
  5502. uroUtils.AddEventListener('_btnClearCamWatchList', "click", uroOWL.ClearCamWatchList, true);
  5503. uroUtils.AddEventListener('_btnSettingsToText', "click", uroConfig.SettingsToText, true);
  5504. uroUtils.AddEventListener('_btnTextToSettings', "click", uroConfig.TextToSettings, true);
  5505. uroUtils.AddEventListener('_btnResetSettings', "click", uroConfig.DefaultSettings, true);
  5506. uroUtils.AddEventListener('_btnClearSettingsText', "click", uroConfig.ClearSettingsText, true);
  5507. uroUtils.AddEventListener('_cbMasterEnable', "click", uroFilterItems_MasterEnableClick, true);
  5508. /*
  5509. uroUtils.AddEventListener('_btnDebugToScreen',"click", uroDBG.Dump, true);
  5510. */
  5511. uroUtils.AddEventListener('uroDiv', "dblclick", uroPopup.Suppress, true);
  5512. uroUtils.AddEventListener('_selectCameraUserID', "change", uroCamEditorSelected, true);
  5513. uroUtils.AddEventListener('_selectPlacesUserID', "change", uroPlacesEditorSelected, true);
  5514. uroUtils.AddEventListener('_selectHidePlacesUserID', "change", uroHidePlacesEditorSelected, true);
  5515. uroUtils.AddEventListener('uroAlertTickBtn', 'click', uroAlertBox.CloseWithTick, true);
  5516. uroUtils.AddEventListener('uroAlertCrossBtn', 'click', uroAlertBox.CloseWithCross, true);
  5517. for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
  5518. {
  5519. uroUtils.SetOnClick(uroTabs.CtrlTabs[i][uroTabs.FIELDS.LINKID], uroTabs.CtrlTabs[i][uroTabs.FIELDS.SHOWFN]);
  5520. }
  5521. for(let idx=0;idx<W.Config.venues.categories.length;idx++)
  5522. {
  5523. uroUtils.SetOnClick('_uroPlacesGroupState-'+idx,uroPlacesGroupCollapseExpand);
  5524. }
  5525. uroDBG.AddLog('finalise onload');
  5526. uroNewLookCheckDetailsRequest();
  5527. // filter markers as and when the map is moved
  5528. W.map.events.register("moveend", null, uroMapMoveEnd.Handler);
  5529. W.map.events.register("moveend", null, uroGetAMs); // uroGetAMs accesses e, so has to be called directly from the event handler
  5530. W.map.events.register("mousemove", null, uroGetAMs);
  5531. W.map.events.register("mousemove", null, uroMarkers.MouseMove);
  5532. W.map.events.registerPriority("mousedown", null, uroMouseDown);
  5533. // trap mousedown on Streetview marker drag
  5534. if(document.getElementsByClassName('street-view-control').length === 0) return;
  5535. document.getElementsByClassName('street-view-control')[0].onmousedown = uroMouseDown;
  5536. W.map.events.register("mouseup", null, uroMouseUp);
  5537. W.map.events.register("mouseout", null, uroMouseOut);
  5538. uroSetSectionTabStyles();
  5539. uroConfig.LoadSettings();
  5540. uroUITweaks.ChangeClustering();
  5541.  
  5542. uroDBG.AddLog('getting user ID...');
  5543. uroUserID = W.loginManager.user.attributes.id;
  5544. uroDBG.AddLog('...ID is '+uroUserID);
  5545. uroDBG.AddLog('filtering...');
  5546. uroFilterItems();
  5547. uroDBG.AddLog('...done');
  5548. uroDBG.showDebugOutput = uroDBG.persistentDebugOutput;
  5549. let dbgMode = "none";
  5550. if(uroDBG.showDebugOutput)
  5551. {
  5552. dbgMode = "inline";
  5553. }
  5554. document.getElementById('_uroDebugMode').style.display = dbgMode;
  5555. uroUtils.AddEventListener('_uroVersion',"click", uroDBG.Toggle, true);
  5556. // add exclusiveCB click handlers to all checkboxes with a pairedWith attribute
  5557. uroDBG.AddLog('adding exclusiveCB handlers...');
  5558. let cbList = document.getElementsByTagName('input');
  5559. for (let optIdx=0;optIdx<cbList.length;optIdx++)
  5560. {
  5561. if((cbList[optIdx].id.indexOf('_cb') === 0) && (cbList[optIdx].attributes.pairedWith != null))
  5562. {
  5563. uroUtils.SetOnClick(cbList[optIdx].id,uroExclusiveCB);
  5564. }
  5565. }
  5566. uroDBG.AddLog('...done');
  5567. // manually call the layer-change handlers on startup, since there's a good chance WME will already have
  5568. // completed its own startup layer changes before our handlers get registered, preventing the marker handlers
  5569. // from being set up as expected on any markers which are visible in the startup map view before the user forces
  5570. // a layer update by panning/zooming/etc...
  5571. uroLayers.RunChangeHandlers();
  5572.  
  5573. uroUITweaks.Setup();
  5574. uroInit.setupListeners = false;
  5575. uroMainTickStage = 0;
  5576. window.clearInterval(uroMainTickHandlerID);
  5577. window.setInterval(uroMainTick, 10);
  5578. uroInit.initialised = true;
  5579. }
  5580. };
  5581. const uroUITweaks = // native UI enhancements
  5582. {
  5583. MO_SidePanel : null,
  5584. MO_ReportPanel : null,
  5585.  
  5586. ChangeMapBGColour: function()
  5587. {
  5588. let mapviewport = document.getElementById("WazeMap").getElementsByClassName("olMapViewport")[0];
  5589. if((uroUtils.GetCBChecked('_cbWhiteBackground') === true) && (uroUtils.GetCBChecked('_cbMasterEnable') === true))
  5590. {
  5591. let customColour = '#' + uroUtils.ToHex(uroUtils.GetElmValue('_inputCustomBackgroundRed'),2);
  5592. customColour += uroUtils.ToHex(uroUtils.GetElmValue('_inputCustomBackgroundGreen'),2);
  5593. customColour += uroUtils.ToHex(uroUtils.GetElmValue('_inputCustomBackgroundBlue'),2);
  5594. mapviewport.style.setProperty('background',customColour,'important');
  5595. }
  5596. else
  5597. {
  5598. mapviewport.style.setProperty('background',"#354148",'important');
  5599. }
  5600. },
  5601. HideAMLayer: function()
  5602. {
  5603. // If this sounds like a weird function - why not just switch off the layer from the layers menu? - then
  5604. // remember that in order for URO+ to be able to display in its own tab the list of AMs under the current
  5605. // mouse pointer location, which is somewhat more useful than the list given in the topbar, it needs the
  5606. // AM layer to be activated so that the AM areas data is loaded into WME. It doesn't however need the layer
  5607. // to then be visible, and since having a bunch of purple polygons covering the map can make for a rather
  5608. // difficult editing experience, being able to hide the polys whilst retaining the area information is
  5609. // of real benefit...
  5610. if((uroUtils.GetCBChecked('_cbHideAMLayer')) && (uroUtils.GetCBChecked('_cbMasterEnable')))
  5611. {
  5612. W.map.managedAreasLayer.setOpacity(0);
  5613. }
  5614. else
  5615. {
  5616. W.map.managedAreasLayer.setOpacity(1);
  5617. }
  5618. },
  5619. HideSegments: function()
  5620. {
  5621. // Hides the vector segments when the raster segment layer is hidden
  5622. if(uroUtils.GetCBChecked('_cbHideSegmentsWhenRoadsHidden'))
  5623. {
  5624. W.map.segmentLayer.drawn = W.map.roadLayer.visibility;
  5625. W.map.nodeLayer.drawn = W.map.roadLayer.visibility;
  5626. }
  5627. else
  5628. {
  5629. W.map.segmentLayer.drawn = true;
  5630. W.map.nodeLayer.drawn = true;
  5631. }
  5632. },
  5633. SetClusteringFor: function(layerID, toDisable)
  5634. {
  5635. let strat = uroLayers.layers[layerID].l.strategies;
  5636. if((strat !== undefined) && (strat.length > 0))
  5637. {
  5638. if(toDisable === true)
  5639. {
  5640. strat[0].threshold = 100000;
  5641. }
  5642. else
  5643. {
  5644. strat[0].threshold = 10;
  5645. }
  5646. }
  5647. },
  5648. ChangeClustering: function()
  5649. {
  5650. uroUITweaks.SetClusteringFor(uroLayers.ID.UR, uroUtils.GetCBChecked('_cbInhibitURClusters'));
  5651. uroUITweaks.SetClusteringFor(uroLayers.ID.MP, uroUtils.GetCBChecked('_cbInhibitMPClusters'));
  5652. uroUITweaks.SetClusteringFor(uroLayers.ID.PUR, uroUtils.GetCBChecked('_cbInhibitPUClusters'));
  5653. uroUITweaks.SetClusteringFor(uroLayers.ID.PPUR, uroUtils.GetCBChecked('_cbInhibitPUClusters'));
  5654. uroUITweaks.SetClusteringFor(uroLayers.ID.RPUR, uroUtils.GetCBChecked('_cbInhibitPUClusters'));
  5655. uroUITweaks.SetClusteringFor(uroLayers.ID.SegSug, uroUtils.GetCBChecked('_cbInhibitESClusters'));
  5656. uroUITweaks.SetClusteringFor(uroLayers.ID.EditSug, uroUtils.GetCBChecked('_cbInhibitESClusters'));
  5657.  
  5658. // If the map is zoomed out far enough such that clustering could be occurring, perform a
  5659. // zoom in-out to force a redraw of the markers based on whatever the new clustering
  5660. // settings are...
  5661. if(W.map.getZoom() < 16)
  5662. {
  5663. W.map.zoomIn();
  5664. W.map.zoomOut();
  5665. }
  5666. },
  5667. ReportPanelChange: function()
  5668. {
  5669. // Inhibit map re-centering when opening a report
  5670. if(uroMarkers.inhibitSetCenter === true)
  5671. {
  5672. let bcr = document.querySelector('#panel-container').getBoundingClientRect();
  5673. if(bcr.width > 0)
  5674. {
  5675. uroMarkers.inhibitSetCenter = false;
  5676. W.map.setCenter(uroMarkers.clickedOnCenter);
  5677. }
  5678. }
  5679.  
  5680. // "panel-container" now also gets used to show the turn closure UI, so reuse this MO handler
  5681. // as a way to also apply the MTE dropdown fix here...
  5682. let mteDropDown = document.querySelector('#panel-container #closure_eventId');
  5683. if(mteDropDown !== undefined)
  5684. {
  5685. uroFixMTEDropDown(mteDropDown);
  5686. }
  5687. },
  5688. CheckForClosurePanel: function()
  5689. {
  5690. if(uroUtils.IsClosureUIActive() === true)
  5691. {
  5692. // Closure panel active
  5693. let uroMO_ClosureUI = new MutationObserver(uroClosureEditUIChanged);
  5694. uroMO_ClosureUI.disconnect();
  5695. uroMO_ClosureUI.observe(document.querySelector('wz-tab.closures-tab'),{subtree: true, attributes: true});
  5696. uroClosureEditUIChanged();
  5697.  
  5698. let cl = document.querySelector('.closures-list');
  5699. if(cl !== null)
  5700. {
  5701. let uroRO_ClosureUI = new ResizeObserver(uroScrollToEndOfClosures);
  5702. uroRO_ClosureUI.disconnect();
  5703. uroRO_ClosureUI.observe(cl);
  5704. }
  5705. uroScrollToEndOfClosures();
  5706. }
  5707. },
  5708. SidePanelChange: function()
  5709. {
  5710. // The sidepanel MO only fires when the sidepanel changes in bulk - i.e. when first rendered, or
  5711. // when changing to show details for a different type of map object. Once this version of the
  5712. // panel is open, any internal changes, such as selecting a different tab within the panel, do
  5713. // NOT trigger the MO. Consequently, if a segment is selected and the sidepanel doesn't then
  5714. // open with the closures tab already selected, the MO will fire at this point rather than at the
  5715. // point where the closures tab is selected...
  5716. //
  5717. // To ensure the closures UI enhancements are applied consistently, we therefore need to set up an
  5718. // onclick handler to deal with the sidepanel opening into a different tab and the user then
  5719. // clicking through into the closures tab after this MO has already fired, but we ALSO still need
  5720. // to check for the availability of the closures UI here as well just in case the sidepanel opens
  5721. // into the closures tab directly.
  5722. //
  5723. // Easy really...
  5724.  
  5725. let elmToClick = document.querySelector('#edit-panel');
  5726. if(elmToClick !== null)
  5727. {
  5728. uroUtils.SetOnClick(elmToClick, uroUITweaks.CheckForClosurePanel);
  5729. }
  5730. uroUITweaks.CheckForClosurePanel();
  5731. },
  5732. Setup: function()
  5733. {
  5734. uroUITweaks.MO_SidePanel = new MutationObserver(uroUITweaks.SidePanelChange);
  5735. uroUITweaks.MO_SidePanel.observe(document.getElementById('edit-panel'), {childList: true, subtree: true});
  5736. uroUITweaks.MO_ReportPanel = new MutationObserver(uroUITweaks.ReportPanelChange);
  5737. uroUITweaks.MO_ReportPanel.observe(document.getElementById('panel-container'), {childList: true, subtree: true});
  5738.  
  5739. uroUITweaks.ChangeMapBGColour();
  5740. uroUITweaks.HideAMLayer();
  5741. uroUITweaks.HideSegments();
  5742. }
  5743. };
  5744. const uroMapMoveEnd = // things that happen after a move of the map view
  5745. {
  5746. lat : null,
  5747. lon: null,
  5748. zoom: null,
  5749. Handler: function()
  5750. {
  5751. let mc = W.map.getCenter();
  5752. let z = W.map.getZoom();
  5753. if((mc.lat != this.lat) || (mc.lon != this.lon) || (z != this.zoom))
  5754. {
  5755. // Apply any filters which need to be updated when the map view changes,
  5756. // and which won't be applied via an event handler or mutation observer
  5757. // attached to the relevant layer etc...
  5758. uroFilterProblems();
  5759. uroFilterPlaces();
  5760. uroFilterCameras();
  5761. uroFilterURs();
  5762. uroFilterRAs();
  5763. uroFilterMapComments();
  5764.  
  5765. uroMiscUITweaksHandler();
  5766. uroLayers.MCLayerChanged();
  5767. this.lat = mc.lat;
  5768. this.lon = mc.lon;
  5769. this.zoom = z;
  5770. }
  5771. }
  5772. };
  5773.  
  5774.  
  5775. // ================================================================================================
  5776. // Here be the unfactored wilderness...
  5777. // ================================================================================================
  5778.  
  5779.  
  5780. function uroFixMTEDropDown(mteDropDown)
  5781. {
  5782. // Auto-selects the "None" event within the MTE dropdown element passed into the function, to avoid the user having
  5783. // to manually select it - the only time you'd need to select something other than None is when you're assigning a
  5784. // closure to a MTE, at which point you need to manually select the appropriate event from the dropdown anyway, so
  5785. // pre-selecting None doesn't increase the workload for setting up a MTE-related closure, and it reduces the workload
  5786. // for setting up other closures...
  5787. //
  5788. // The only possible negative to this hack is that it means the user can set up a MTE-related closure without being
  5789. // reminded by WME to select the appropriate event from the list, because now that we're defaulting it to None, WME
  5790. // will allow the closure to be set without complaining that no event is set... But TBH, that's a small price to pay
  5791. // compared with the far, FAR, larger irritation of forcing users to always select None for all the closures that
  5792. // get added every single day without ever needing to be associated with a MTE - if a handful of closures end up being
  5793. // added with the user having forgotten to select the MTE, then no biggie. The closure will still at least have been
  5794. // added and its effect on routing around the event will therefore still be just as it would be if the MTE had been
  5795. // associated with the closure - the only difference is that if someone then bothers to look at the overview map of the
  5796. // MTE, they won't see that particular segment listed as a closure. I can live with the tiny risk of that causing any
  5797. // real problems, when weighed up against the millions of extra clicks saved through pre-selecting None...
  5798.  
  5799. let retval = false;
  5800.  
  5801. // Make sure the closure event list is available, and that we haven't already messed with it.
  5802. if((mteDropDown !== null) && (mteDropDown.tag != "touchedByURO"))
  5803. {
  5804. // The event dropdown is now some byzantine piece of DOM manipulation to generate something which looks like a
  5805. // regular select list, but which can't be manipulated like one... The first gotcha is that the selected item
  5806. // exists only within a shadow DOM section within the dropdown rather than simply being part of the list from
  5807. // which we'd be able to read off its selected index. So to check whether or not the user has selected an
  5808. // event already, we need to drill down into this shadow DOM to get its text contents, and compare those against
  5809. // the I18n translation for the choose event text. What a palaver...
  5810. let shadowElm = mteDropDown.shadowRoot.querySelectorAll('.selected-value')[0];
  5811. if(shadowElm !== undefined)
  5812. {
  5813. let eventText = mteDropDown.shadowRoot.querySelectorAll('.selected-value')[0].innerText;
  5814. // Sometimes we get here before WME has finished rendering, so if the event text hasn't been set yet then we
  5815. // need to return false and let the caller deal with it...
  5816. if(eventText !== '')
  5817. {
  5818. if(eventText == I18n.lookup('closures.choose_event'))
  5819. {
  5820. // Having now established that, yes, the closure hasn't yet been associated with any event, it's surprisingly
  5821. // easy to change it to "None" - we just generate a click event on the first child element in the main DOM (not
  5822. // the shadow DOM this time), which replicates what the user would do to select None manually.
  5823. mteDropDown.children[0].click();
  5824. }
  5825. // Tag the event list to prevent further processing attempts whilst the edit UI for this closure remains open.
  5826. mteDropDown.tag = "touchedByURO";
  5827. retval = true;
  5828. }
  5829. }
  5830. }
  5831.  
  5832. return retval;
  5833. }
  5834. let uroPendingURSessionsTotal;
  5835. let uroFinalizeTimeoutHandle = null;
  5836. function uroFinalizeURSessionsGet()
  5837. {
  5838. if(uroPendingURSessionsTotal != uroPendingURSessionIDs.length)
  5839. {
  5840. uroPendingURSessionsTotal = uroPendingURSessionIDs.length;
  5841. if(uroFinalizeTimeoutHandle !== null)
  5842. {
  5843. window.clearTimeout(uroFinalizeTimeoutHandle);
  5844. uroFinalizeTimeoutHandle = null;
  5845. }
  5846. uroFinalizeTimeoutHandle = window.setTimeout(uroFinalizeURSessionsGet, 500);
  5847. return;
  5848. }
  5849.  
  5850. let idList = [];
  5851.  
  5852. while((idList.length < 50) && (uroPendingURSessionIDs.length))
  5853. {
  5854. let id = uroPendingURSessionIDs.shift();
  5855. idList.push(id);
  5856. }
  5857.  
  5858. if(idList.length > 0)
  5859. {
  5860. uroDBG.AddLog('grabbing '+idList.length+' updateRequestSessions, IDs: '+idList);
  5861. W.model.updateRequestSessions.getAsync(idList);
  5862. }
  5863.  
  5864. if((uroPendingURSessionIDs.length) || (uroRequestedURSessionIDs.length))
  5865. {
  5866. window.setTimeout(uroGetUpdateRequestSessions,1000);
  5867. }
  5868. else
  5869. {
  5870. uroPopulatingRequestSessions = false;
  5871. }
  5872. }
  5873. function uroGetUpdateRequestSessions()
  5874. {
  5875. uroPendingURSessionsTotal = uroPendingURSessionIDs.length;
  5876. if(uroFinalizeTimeoutHandle !== null)
  5877. {
  5878. window.clearTimeout(uroFinalizeTimeoutHandle);
  5879. uroFinalizeTimeoutHandle = null;
  5880. }
  5881. uroFinalizeTimeoutHandle = window.setTimeout(uroFinalizeURSessionsGet,500);
  5882. }
  5883. function uroRefreshUpdateRequestSessions()
  5884. {
  5885. let urcount = 0;
  5886. uroPendingURSessionIDs = [];
  5887. uroRequestedURSessionIDs = [];
  5888. uroPopulatingRequestSessions = true;
  5889. for (let urID in W.model.mapUpdateRequests.objects)
  5890. {
  5891. if(W.model.mapUpdateRequests.objects.hasOwnProperty(urID))
  5892. {
  5893. if(W.model.updateRequestSessions.objects[urID] === undefined)
  5894. {
  5895. uroPendingURSessionIDs.push(urID);
  5896. }
  5897. urcount++;
  5898. }
  5899. }
  5900. uroGetUpdateRequestSessions();
  5901. }
  5902. function uroURHasMyComments(fid)
  5903. {
  5904. if(uroUserID === -1)
  5905. {
  5906. return false;
  5907. }
  5908. let nComments = W.model.updateRequestSessions.objects[fid].attributes.comments.length;
  5909. if(nComments === 0)
  5910. {
  5911. return false;
  5912. }
  5913.  
  5914. for(let cidx=0; cidx<nComments; cidx++)
  5915. {
  5916. if(W.model.updateRequestSessions.objects[fid].attributes.comments[cidx].userID == uroUserID)
  5917. {
  5918. return true;
  5919. }
  5920. }
  5921.  
  5922. return false;
  5923. }
  5924. function uroIsFilteringEnabled(ignoreZoom)
  5925. {
  5926. let retval = false;
  5927. if
  5928. (
  5929. (uroUtils.GetCBChecked('_cbMasterEnable') === true) &&
  5930. (
  5931. (ignoreZoom === true) ||
  5932. (W.map.getZoom() <= uroUtils.GetElmValue('_inputFilterMinZoomLevel'))
  5933. )
  5934. )
  5935. {
  5936. retval = true;
  5937. }
  5938. return retval;
  5939. }
  5940. function uroUpdateMTEList()
  5941. {
  5942. if(Object.keys(W.model.majorTrafficEvents.objects).length === 0) return;
  5943.  
  5944. let selectedIdx = null;
  5945. let idx;
  5946. let mteNames = [];
  5947. let mteIDs = [];
  5948. for(idx in W.model.majorTrafficEvents.objects)
  5949. {
  5950. if(W.model.majorTrafficEvents.objects.hasOwnProperty(idx))
  5951. {
  5952. let name = W.model.majorTrafficEvents.objects[idx].attributes.names[0]?.value;
  5953. if(mteNames.indexOf(name) == -1)
  5954. {
  5955. mteNames.push(name);
  5956. mteIDs.push(idx);
  5957. }
  5958. }
  5959. }
  5960. // check for any previously selected ID in the list, then clear it and repopulate
  5961. // using the newly gathered ID collection from above, and finally reselect the
  5962. // previously selected MTE if its still present in the new list...
  5963. let selector;
  5964. let selectedID;
  5965. let selectorEntry;
  5966.  
  5967. selector = document.getElementById('_selectRTCMTE');
  5968. selectedID = null;
  5969. if(selector.selectedOptions[0] != null)
  5970. {
  5971. selectedID = selector.selectedOptions[0].value;
  5972. }
  5973. while(selector.options.length > 0)
  5974. {
  5975. selector.options.remove(0);
  5976. }
  5977. selector.options.add(new Option('<select a MTE>', null));
  5978. if(mteNames.length > 0)
  5979. {
  5980. selectorEntry = '';
  5981. for(idx=0; idx<mteNames.length; idx++)
  5982. {
  5983. selectorEntry = mteNames[idx];
  5984. selector.options.add(new Option(selectorEntry, mteIDs[idx]));
  5985. if(mteIDs[idx] == selectedID)
  5986. {
  5987. selectedIdx = idx+1;
  5988. }
  5989. }
  5990. }
  5991.  
  5992. if(selectedIdx !== null)
  5993. {
  5994. selector.selectedIndex = selectedIdx;
  5995. }
  5996. }
  5997. function uroRTCMarkerInfo(mIdx, isVisible)
  5998. {
  5999. let pri = null;
  6000. let status = null;
  6001. let dir = null;
  6002. let pos = null;
  6003. let mID = null;
  6004.  
  6005. let mObj = uroLayers.layers[uroLayers.ID.RTC].l.markers[mIdx];
  6006. if(mObj !== undefined)
  6007. {
  6008. // Store the marker status - if we've already made a note of the original
  6009. // status by adding an "orig_"-prefixed classname, extract the status from
  6010. // that rather than from the class currently being used to display the
  6011. // marker, to preserve the original status regardless of what we may have
  6012. // done to it subsequently as a result of our RTC filtering setup...
  6013. let cList = mObj.element.classList;
  6014. for(let i = 0; i < cList.length; ++i)
  6015. {
  6016. if(cList[i].indexOf('orig_') != -1)
  6017. {
  6018. status = cList[i].replace('orig_', '');
  6019. }
  6020. }
  6021. if(status === null)
  6022. {
  6023. for(let i = 0; i < cList.length; ++i)
  6024. {
  6025. if(cList[i].indexOf('status-') != -1)
  6026. {
  6027. status = cList[i];
  6028. }
  6029. }
  6030. }
  6031.  
  6032. // Assign a priority level to the marker so we know whether to show or hide
  6033. // it if it's stacked up with others on the same segment - the level set here
  6034. // follows the priority used by WME to decide which closures to show normally.
  6035. if(status == "status-active")
  6036. {
  6037. pri = 2;
  6038. }
  6039. else if(status == "status-not-started")
  6040. {
  6041. pri = 1;
  6042. }
  6043. else
  6044. {
  6045. pri = 0;
  6046. }
  6047.  
  6048. // To avoid the need to cross-reference with the closure model object, use the
  6049. // classname of the arrow attached to this marker to determine if the closure is
  6050. // in the forward or reverse direction.
  6051. if(mObj.element.childNodes.length == 1)
  6052. {
  6053. if(mObj.element.childNodes[0].className.indexOf('forward') != -1)
  6054. {
  6055. dir = "fwd";
  6056. }
  6057. else if(mObj.element.childNodes[0].className.indexOf('backward') != -1)
  6058. {
  6059. dir = "rev";
  6060. }
  6061. }
  6062.  
  6063. pos = mObj.px;
  6064. mID = mObj.element.dataset.id;
  6065. }
  6066.  
  6067. this.isVisible = isVisible;
  6068. this.pos = pos;
  6069. this.pri = pri;
  6070. this.mID = mID;
  6071. this.status = status;
  6072. this.dir = dir;
  6073. }
  6074. function uroFilterRTCs()
  6075. {
  6076. let pmTStart = performance.now();
  6077. let pmFunction = "uroFilterRTCs";
  6078.  
  6079. if(uroFilterPreamble() === false) return;
  6080.  
  6081. let closureLayer = uroLayers.layers[uroLayers.ID.RTC].l;
  6082. if(closureLayer.markers.length === 0) return;
  6083.  
  6084. let uFR_filterActiveFromWME = uroUtils.GetCBChecked('_cbHideEditorRTCs');
  6085. let uFR_filterActiveFromWazeFeed = uroUtils.GetCBChecked('_cbHideWazeFeedRTCs');
  6086. let uFR_filterActiveFromWazeOther = uroUtils.GetCBChecked('_cbHideWazeRTCs');
  6087. let uFR_filterFutureFromWME = uroUtils.GetCBChecked('_cbHideFutureEditorRTCs');
  6088. let uFR_filterFutureFromWazeFeed = uroUtils.GetCBChecked('_cbHideFutureWazeFeedRTCs');
  6089. let uFR_filterFutureFromWazeOther = uroUtils.GetCBChecked('_cbHideFutureWazeRTCs');
  6090. let uFR_filterExpiredFromWME = uroUtils.GetCBChecked('_cbHideExpiredEditorRTCs');
  6091. let uFR_filterExpiredFromWazeFeed = uroUtils.GetCBChecked('_cbHideExpiredWazeFeedRTCs');
  6092. let uFR_filterExpiredFromWazeOther = uroUtils.GetCBChecked('_cbHideExpiredWazeRTCs');
  6093. let uFR_filterUnknownFromWME = uroUtils.GetCBChecked('_cbHideUnknownEditorRTCs');
  6094. let uFR_filterUnknownFromWazeFeed = uroUtils.GetCBChecked('_cbHideUnknownWazeFeedRTCs');
  6095. let uFR_filterUnknownFromWazeOther = uroUtils.GetCBChecked('_cbHideUnknownWazeRTCs');
  6096. let uFR_filterShowForMTE = uroUtils.GetCBChecked('_cbShowMTERTCs');
  6097. let uFR_filterHideForMTE = uroUtils.GetCBChecked('_cbHideMTERTCs');
  6098. let uFR_filterHideDurationLessThan = uroUtils.GetCBChecked('_cbEnableRTCDurationFilterLessThan');
  6099. let uFR_filterHideDurationMoreThan = uroUtils.GetCBChecked('_cbEnableRTCDurationFilterMoreThan');
  6100. let uFR_thresholdDurationLessThan = uroUtils.GetElmValue('_inputFilterRTCDurationLessThan');
  6101. let uFR_thresholdDurationMoreThan = uroUtils.GetElmValue('_inputFilterRTCDurationMoreThan');
  6102.  
  6103. let uFR_filterShowForTS = uroUtils.GetCBChecked('_cbRTCFilterShowForTS');
  6104. let uFR_filterHideForTS = uroUtils.GetCBChecked('_cbRTCFilterHideForTS');
  6105. let tsD = uroUtils.GetElmValue('_inputRTCFilterDay');
  6106. let tsMo = uroUtils.GetElmValue('_inputRTCFilterMonth');
  6107. let tsY = uroUtils.GetElmValue('_inputRTCFilterYear');
  6108. let tsH = uroUtils.GetElmValue('_inputRTCFilterHour');
  6109. let tsMi = uroUtils.GetElmValue('_inputRTCFilterMin');
  6110. let filterTS = uroUtils.GetTS(tsD, tsMo, tsY, tsH, tsMi);
  6111. uroUpdateMTEList();
  6112. let mteID = null;
  6113. let selectorMTE = document.getElementById('_selectRTCMTE');
  6114. if(selectorMTE?.selectedOptions[0] != null)
  6115. {
  6116. mteID = selectorMTE.selectedOptions[0].value;
  6117. }
  6118.  
  6119. let uFR_masterEnable = uroIsFilteringEnabled(false);
  6120.  
  6121. let markerInfo = [];
  6122.  
  6123. // Pass 1 - determine which filtering to apply to each of the RTC markers
  6124. let markerIdx = 0;
  6125. for (let rtcObj in W.model.roadClosures.objects)
  6126. {
  6127. let isVisible = true;
  6128.  
  6129. if(uFR_masterEnable === true)
  6130. {
  6131. let rtcModel = W.model.roadClosures.objects[rtcObj];
  6132.  
  6133. if(mteID !== null)
  6134. {
  6135. if((uFR_filterShowForMTE === true) && (rtcModel.attributes.eventId !== mteID))
  6136. {
  6137. isVisible = false;
  6138. }
  6139. if((uFR_filterHideForMTE === true) && (rtcModel.attributes.eventId === mteID))
  6140. {
  6141. isVisible = false;
  6142. }
  6143. }
  6144.  
  6145. let rtcType = uroGetRTCOrigin(rtcModel);
  6146. let rtcState = uroGetRTCState(rtcModel);
  6147.  
  6148. if(rtcType == uroEnums.TRTC.WAZEFEED)
  6149. {
  6150. if
  6151. (
  6152. ((rtcState === uroEnums.SRTC.ACTIVE) && (uFR_filterActiveFromWazeFeed === true)) ||
  6153. ((rtcState === uroEnums.SRTC.FUTURE) && (uFR_filterFutureFromWazeFeed === true)) ||
  6154. ((rtcState === uroEnums.SRTC.EXPIRED) && (uFR_filterExpiredFromWazeFeed === true)) ||
  6155. ((rtcState === uroEnums.SRTC.UNKNOWN) && (uFR_filterUnknownFromWazeFeed === true))
  6156. )
  6157. {
  6158. isVisible = false;
  6159. }
  6160. }
  6161. else if(rtcType == uroEnums.TRTC.WAZEOTHER)
  6162. {
  6163. if
  6164. (
  6165. ((rtcState === uroEnums.SRTC.ACTIVE) && (uFR_filterActiveFromWazeOther === true)) ||
  6166. ((rtcState === uroEnums.SRTC.FUTURE) && (uFR_filterFutureFromWazeOther === true)) ||
  6167. ((rtcState === uroEnums.SRTC.EXPIRED) && (uFR_filterExpiredFromWazeOther === true)) ||
  6168. ((rtcState === uroEnums.SRTC.UNKNOWN) && (uFR_filterUnknownFromWazeOther === true))
  6169. )
  6170. {
  6171. isVisible = false;
  6172. }
  6173. }
  6174. else
  6175. {
  6176. if
  6177. (
  6178. ((rtcState === uroEnums.SRTC.ACTIVE) && (uFR_filterActiveFromWME === true)) ||
  6179. ((rtcState === uroEnums.SRTC.FUTURE) && (uFR_filterFutureFromWME === true)) ||
  6180. ((rtcState === uroEnums.SRTC.EXPIRED) && (uFR_filterExpiredFromWME === true)) ||
  6181. ((rtcState === uroEnums.SRTC.UNKNOWN) && (uFR_filterUnknownFromWME === true))
  6182. )
  6183. {
  6184. isVisible = false;
  6185. }
  6186. }
  6187. let rtcDuration = uroGetRTCDuration(rtcModel);
  6188. if(uFR_filterHideDurationLessThan === true)
  6189. {
  6190. if(rtcDuration < uFR_thresholdDurationLessThan)
  6191. {
  6192. isVisible = false;
  6193. }
  6194. }
  6195. if(uFR_filterHideDurationMoreThan === true)
  6196. {
  6197. if(rtcDuration > uFR_thresholdDurationMoreThan)
  6198. {
  6199. isVisible = false;
  6200. }
  6201. }
  6202. if((uFR_filterShowForTS === true) || (uFR_filterHideForTS === true))
  6203. {
  6204. let startTS = new Date(rtcModel.attributes.startDate).getTime();
  6205. let endTS = new Date(rtcModel.attributes.endDate).getTime();
  6206. if(uFR_filterShowForTS === true)
  6207. {
  6208. if((filterTS < startTS) || (filterTS > endTS))
  6209. {
  6210. isVisible = false;
  6211. }
  6212. }
  6213. if (uFR_filterHideForTS === true)
  6214. {
  6215. if((filterTS >= startTS) && (filterTS <= endTS))
  6216. {
  6217. isVisible = false;
  6218. }
  6219. }
  6220. }
  6221. }
  6222.  
  6223. markerInfo.push(new uroRTCMarkerInfo(markerIdx, isVisible));
  6224. ++markerIdx;
  6225. }
  6226.  
  6227. // Pass 2 - based on the initial filtering results, determine which markers *actually* should be
  6228. // made visible or hidden according both to our filtering settings AND any masking of this marker
  6229. // due to the presence of other higher priority markers at the same position which have also been
  6230. // marked as visible following the filtering pass...
  6231. //
  6232. // For added merriment, we also deal with the WME limitation that prevents it displaying two
  6233. // different types of closure arrow if a segment has a one-way closure which is a higher priority
  6234. // than a closure in the opposite direction.
  6235.  
  6236. let cNodesDiv = uroLayers.layers[uroLayers.ID.RTCnode].l.div;
  6237. for (let i = 0; i < markerIdx; ++i)
  6238. {
  6239. let status = markerInfo[i].status;
  6240.  
  6241. // Only apply this pass to closures which haven't already been hidden in pass 1, AND which
  6242. // have a valid marker position
  6243. if((markerInfo[i].isVisible === true) && (markerInfo[i].pos !== null))
  6244. {
  6245. // Iterate through all the other markers, looking for any which are also still visible
  6246. // and have the same marker position as the one we're currently processing
  6247. for (let j = 0; j < markerIdx; ++j)
  6248. {
  6249. if(j != i)
  6250. {
  6251. if((markerInfo[j].isVisible === true) && (markerInfo[j].pos !== null))
  6252. {
  6253. if((markerInfo[i].pos.x == markerInfo[j].pos.x) && (markerInfo[i].pos.y == markerInfo[j].pos.y))
  6254. {
  6255. if(markerInfo[j].pri > markerInfo[i].pri)
  6256. {
  6257. if(markerInfo[j].dir == markerInfo[i].dir)
  6258. {
  6259. // Mark the currently processed marker to be hidden only if this higher priority
  6260. // marker is for a closure in the same direction - if it's not, then we need to
  6261. // leave the current marker visible so that its arrow remains visible...
  6262. markerInfo[i].isVisible = false;
  6263. break;
  6264. }
  6265. else
  6266. {
  6267. // Otherwise, if we're leaving the current marker visible then we'll need to
  6268. // alter its status class to match this higher-priority marker, so that the
  6269. // marker which is shown to the user remains correct regardless of what the
  6270. // relative stacking order of the markers is on this segment.
  6271. status = markerInfo[j].status;
  6272. }
  6273. }
  6274. }
  6275. }
  6276. }
  6277. }
  6278. }
  6279.  
  6280. let marker = closureLayer.markers[i].element;
  6281. let markerClass = marker.className;
  6282.  
  6283. // Remove the hidden class if present - this allows markers natively hidden by WME due to being masked
  6284. // by higher priority markers to become visible again if our own filtering settings have hidden those
  6285. // higher priority marker...
  6286. markerClass = markerClass.replace(" road-closure-hidden", "");
  6287. if(markerInfo[i].isVisible == false)
  6288. {
  6289. // Apply the hidden class for any markers WE'VE decided need to be hidden
  6290. markerClass += " road-closure-hidden";
  6291. }
  6292. else
  6293. {
  6294. // For any markers which we're leaving visible, first check to see if it's a marker we haven't yet
  6295. // seen. If so, then we need to store its original status class for future reference.
  6296. if(markerClass.indexOf('orig_') == -1)
  6297. {
  6298. markerClass += (' orig_'+markerInfo[i].status);
  6299. }
  6300. // Now, to ensure this segment displays the appropriate marker for the highest priority closure
  6301. // still visible on it, we remove the existing status class and replace it with the one we
  6302. // chose above
  6303. markerClass = markerClass.replace(" status-finished"," ");
  6304. markerClass = markerClass.replace(" status-active"," ");
  6305. markerClass = markerClass.replace(" status-not-started"," ");
  6306. markerClass += ' '+status;
  6307. }
  6308. marker.className = markerClass;
  6309. let toHide = cNodesDiv.querySelectorAll("[data-id='"+markerInfo[i].mID+"']");
  6310. for(let j = 0; j < toHide.length; ++j)
  6311. {
  6312. if(markerInfo[i].isVisible === false)
  6313. {
  6314. toHide[j].style.visibility = "hidden";
  6315. }
  6316. else
  6317. {
  6318. toHide[j].style.visibility = "";
  6319. }
  6320. }
  6321. }
  6322.  
  6323. uroDBG.PerfMon(pmFunction, pmTStart);
  6324. }
  6325. function uroUpdateRAList()
  6326. {
  6327. if(Object.keys(W.model.restrictedDrivingAreas.objects).length === 0) return;
  6328.  
  6329. let selectedIdx = null;
  6330. let idx;
  6331. let raNames = [];
  6332. for(idx in W.model.restrictedDrivingAreas.objects)
  6333. {
  6334. if(W.model.restrictedDrivingAreas.objects.hasOwnProperty(idx))
  6335. {
  6336. let name = W.model.restrictedDrivingAreas.objects[idx].attributes.name;
  6337. if(raNames.indexOf(name) == -1)
  6338. {
  6339. raNames.push(name);
  6340. }
  6341. }
  6342. }
  6343. // check for any previously selected name in the list, then clear it and repopulate
  6344. // using the newly gathered collection from above, and finally reselect the
  6345. // previously selected MTE if its still present in the new list...
  6346. let selector;
  6347. let selectedName;
  6348. let selectorEntry;
  6349.  
  6350. selector = document.getElementById('_selectRA');
  6351. selectedName = null;
  6352. if(selector.selectedOptions[0] != null)
  6353. {
  6354. selectedName = selector.selectedOptions[0].value;
  6355. }
  6356. while(selector.options.length > 0)
  6357. {
  6358. selector.options.remove(0);
  6359. }
  6360. selector.options.add(new Option('<select a RA>', null));
  6361. if(raNames.length > 0)
  6362. {
  6363. selectorEntry = '';
  6364. for(idx=0; idx<raNames.length; idx++)
  6365. {
  6366. selectorEntry = raNames[idx];
  6367. selector.options.add(new Option(selectorEntry, selectorEntry));
  6368. if(selectorEntry == selectedName)
  6369. {
  6370. selectedIdx = idx+1;
  6371. }
  6372. }
  6373. }
  6374.  
  6375. if(selectedIdx !== null)
  6376. {
  6377. selector.selectedIndex = selectedIdx;
  6378. }
  6379. }
  6380. function uroUpdateEditorList(modelObj, listElement, useCreated, useUpdated, useResolved, useCommenter)
  6381. {
  6382. if(Object.keys(modelObj).length === 0) return;
  6383.  
  6384. let selector = document.getElementById(listElement);
  6385.  
  6386. let selectedUser = null;
  6387. if(selector.selectedOptions[0] != null)
  6388. {
  6389. selectedUser = parseInt(selector.selectedOptions[0].value);
  6390. }
  6391.  
  6392. while(selector.options.length > 0)
  6393. {
  6394. selector.options.remove(0);
  6395. }
  6396.  
  6397. let selectedIdx = null;
  6398. let listedIDs = [];
  6399. let idx;
  6400. for(idx in modelObj)
  6401. {
  6402. if(modelObj.hasOwnProperty(idx))
  6403. {
  6404. let obj;
  6405. if(useCommenter == true)
  6406. {
  6407. obj = modelObj[idx];
  6408. if(obj.attributes.comments.length > 0)
  6409. {
  6410. for(let cidx=0; cidx < obj.attributes.comments.length; cidx++)
  6411. {
  6412. let userID = obj.attributes.comments[cidx].userID;
  6413. if((listedIDs.indexOf(userID) == -1) && (userID != -1))
  6414. {
  6415. listedIDs.push(userID);
  6416. }
  6417. }
  6418. }
  6419. }
  6420. else
  6421. {
  6422. obj = modelObj[idx].attributes;
  6423. let cbID = null;
  6424. let ubID = null;
  6425. let rbID = null;
  6426. if(useCreated == true) cbID = obj.createdBy;
  6427. if(useUpdated == true) ubID = obj.updatedBy;
  6428. if(useResolved == true) ubID = obj.resolvedBy;
  6429. if((cbID !== null) && (listedIDs.indexOf(cbID) == -1))
  6430. {
  6431. listedIDs.push(cbID);
  6432. }
  6433. if((ubID !== null) && (listedIDs.indexOf(ubID) == -1))
  6434. {
  6435. listedIDs.push(ubID);
  6436. }
  6437. if((rbID !== null) && (listedIDs.indexOf(rbID) == -1))
  6438. {
  6439. listedIDs.push(rbID);
  6440. }
  6441. }
  6442. }
  6443. }
  6444.  
  6445. selector.options.add(new Option('<select a user>', null));
  6446. if(listedIDs.length > 0)
  6447. {
  6448. let users = W.model.users.getByIds(listedIDs);
  6449. let selectorEntry = '';
  6450. for(idx=0; idx<users.length; idx++)
  6451. {
  6452. if(listedIDs.indexOf(users[idx].id) != -1)
  6453. {
  6454. listedIDs.splice(listedIDs.indexOf(users[idx]), 1);
  6455. }
  6456. if(users[idx].attributes.userName === undefined)
  6457. {
  6458. selectorEntry = users[idx].attributes.id;
  6459. }
  6460. else
  6461. {
  6462. selectorEntry = users[idx].attributes.userName;
  6463. }
  6464. selector.options.add(new Option(selectorEntry, users[idx].id));
  6465. if(users[idx].attributes.id == selectedUser)
  6466. {
  6467. selectedIdx = idx+1;
  6468. }
  6469. }
  6470. }
  6471.  
  6472. if(selectedIdx !== null)
  6473. {
  6474. selector.selectedIndex = selectedIdx;
  6475. }
  6476. }
  6477. function uroGetUserID(filterNameID, tbUserName)
  6478. {
  6479. if(filterNameID === null)
  6480. {
  6481. for(let idx in W.model.users.objects)
  6482. {
  6483. if(W.model.users.objects.hasOwnProperty(idx))
  6484. {
  6485. if(W.model.users.objects[idx].attributes.userName == tbUserName)
  6486. {
  6487. filterNameID = W.model.users.objects[idx].attributes.id;
  6488. break;
  6489. }
  6490. }
  6491. }
  6492. }
  6493. return filterNameID;
  6494. }
  6495. function uroFilterRAs()
  6496. {
  6497. let pmTStart = performance.now();
  6498. let pmFunction = "uroFilterRAs";
  6499.  
  6500. if(uroFilterPreamble() === false) return;
  6501. let uFURs_masterEnable = uroIsFilteringEnabled(false);
  6502. let filterByArea = uroUtils.GetCBChecked('_cbShowSpecificRA');
  6503. let filterByLastEditor = uroUtils.GetCBChecked('_cbRAEditorIDFilter');
  6504. let filterByMinAge = uroUtils.GetCBChecked('_cbEnableRAAgeFilterLessThan');
  6505. let filterByMaxAge = uroUtils.GetCBChecked('_cbEnableRAAgeFilterMoreThan');
  6506. let thresholdMinAge = uroUtils.GetElmValue('_inputFilterRAAgeLessThan');
  6507. let thresholdMaxAge = uroUtils.GetElmValue('_inputFilterRAAgeMoreThan');
  6508. let selectorRA = document.getElementById('_selectRA');
  6509. if(filterByArea === false)
  6510. {
  6511. while(selectorRA.options.length > 0)
  6512. {
  6513. selectorRA.options.remove(0);
  6514. }
  6515. }
  6516. let shownRA = null;
  6517. if(filterByArea === true)
  6518. {
  6519. if(selectorRA.options.length === 0)
  6520. {
  6521. uroUpdateRAList();
  6522. }
  6523. if(selectorRA.selectedOptions[0] != null)
  6524. {
  6525. shownRA = selectorRA.selectedOptions[0].value;
  6526. }
  6527. }
  6528.  
  6529. let filterNameID = null;
  6530. if(filterByLastEditor == true)
  6531. {
  6532. uroUpdateEditorList(W.model.restrictedDrivingAreas.objects, '_selectRAEditorID', true, true, false, false);
  6533. let selector = document.getElementById('_selectRAEditorID');
  6534. if(selector.selectedIndex > 0)
  6535. {
  6536. filterNameID = document.getElementById('_selectRAEditorID').selectedOptions[0].value;
  6537. }
  6538. }
  6539. let nRANames = document.querySelectorAll('.restricted-driving-area-name-marker').length;
  6540. for (let raIdx = 0; raIdx < W.map.restrictedDrivingAreaLayer.features.length; raIdx++)
  6541. {
  6542. let raObj = W.map.restrictedDrivingAreaLayer.features[raIdx].attributes.wazeFeature;
  6543. if(raObj !== undefined)
  6544. {
  6545. let raStyle = 'visible';
  6546. if(uFURs_masterEnable === true)
  6547. {
  6548. if(shownRA !== null)
  6549. {
  6550. if(raObj._wmeObject.attributes.name != shownRA) raStyle = 'hidden';
  6551. }
  6552. if(filterNameID !== null)
  6553. {
  6554. if((raObj._wmeObject.attributes.createdBy != filterNameID) && (raObj._wmeObject.attributes.updatedBy != filterNameID))
  6555. {
  6556. raStyle = 'hidden';
  6557. }
  6558. }
  6559. let raAge = uroUtils.DateToDays(raObj._wmeObject.attributes.updatedOn);
  6560. if(filterByMinAge == true)
  6561. {
  6562. if(raAge < thresholdMinAge) raStyle = 'hidden';
  6563. }
  6564. if(filterByMaxAge == true)
  6565. {
  6566. if(raAge > thresholdMaxAge) raStyle = 'hidden';
  6567. }
  6568. }
  6569. let geoID = W.map.restrictedDrivingAreaLayer.features[raIdx].geometry.id;
  6570. if(document.getElementById(geoID) !== null)
  6571. {
  6572. document.getElementById(geoID).style.visibility = raStyle;
  6573. }
  6574.  
  6575. // This doesn't always work, as the order in which the markers are listed on their layer isn't guaranteed
  6576. // to match the order in which the corresponding RA polys are listed on theirs...
  6577. if(raIdx < nRANames)
  6578. {
  6579. document.querySelectorAll('.restricted-driving-area-name-marker')[raIdx].style.visibility = raStyle;
  6580. }
  6581. }
  6582. }
  6583. uroDBG.PerfMon(pmFunction, pmTStart);
  6584. }
  6585. function uroFilterPlaceMarker(mObj, vObj, uFP_masterEnable)
  6586. {
  6587. if((mObj === undefined) || (vObj === undefined))
  6588. {
  6589. return;
  6590. }
  6591.  
  6592. let purAge = null;
  6593. let placeStyle = 'visible';
  6594. let hasBalloon = false;
  6595.  
  6596. if(uFP_masterEnable === true)
  6597. {
  6598. if(uro_uFP[uroEnums.FP_OPTS.filterInsideManagedAreas] === true)
  6599. {
  6600. let tPt = [];
  6601. tPt.push(vObj.attributes.geoJSONGeometry.coordinates[0]);
  6602. tPt.push(vObj.attributes.geoJSONGeometry.coordinates[1]);
  6603. if(uroCheckGeometryWithinManagedAreas(tPt) === true) placeStyle = 'hidden';
  6604. }
  6605.  
  6606. if((placeStyle == 'visible') && (uro_uFP[uroEnums.FP_OPTS.filterUneditable] === true))
  6607. {
  6608. if(vObj.attributes.permissions === 0)
  6609. {
  6610. placeStyle = 'hidden';
  6611. }
  6612. if((placeStyle == 'visible') && (uro_uFP[uroEnums.FP_OPTS.isLoggedIn]))
  6613. {
  6614. if(uro_uFP[uroEnums.FP_OPTS.userRank] < vObj.attributes.lockRank)
  6615. {
  6616. placeStyle = 'hidden';
  6617. }
  6618. }
  6619. if((placeStyle == 'visible') && (vObj.attributes.adLocked))
  6620. {
  6621. placeStyle = 'hidden';
  6622. }
  6623. }
  6624.  
  6625. if((placeStyle == 'visible') && (uro_uFP[uroEnums.FP_OPTS.filterLockRanked] === true))
  6626. {
  6627. if(vObj.attributes.lockRank !== 0)
  6628. {
  6629. placeStyle = 'hidden';
  6630. }
  6631. }
  6632.  
  6633. let urEntries = vObj.attributes.venueUpdateRequests;
  6634. if((placeStyle == 'visible') && (urEntries !== undefined))
  6635. {
  6636. hasBalloon = (urEntries.length > 1);
  6637.  
  6638. for(let i = 0; i < urEntries.length; ++i)
  6639. {
  6640. let ut = urEntries[i].attributes.updateType;
  6641. if((uro_uFP[uroEnums.FP_OPTS.filterFlagged] === true) && (ut === "flag"))
  6642. {
  6643. placeStyle = 'hidden';
  6644. }
  6645. else if((uro_uFP[uroEnums.FP_OPTS.filterNewPlace] === true) && (ut === "ADD_VENUE"))
  6646. {
  6647. placeStyle = 'hidden';
  6648. }
  6649. else if((uro_uFP[uroEnums.FP_OPTS.filterUpdatedDetails] === true) && (ut === "UPDATE_VENUE"))
  6650. {
  6651. placeStyle = 'hidden';
  6652. }
  6653. else if((uro_uFP[uroEnums.FP_OPTS.filterNewPhoto] === true) && (ut === "ADD_IMAGE"))
  6654. {
  6655. placeStyle = 'hidden';
  6656. }
  6657. }
  6658.  
  6659. if((placeStyle == 'visible') && (uro_uFP[uroEnums.FP_OPTS.filterOnCFs] === true))
  6660. {
  6661. let nVUR = urEntries.length;
  6662. while(nVUR > 0)
  6663. {
  6664. nVUR--;
  6665. let tCF = urEntries[nVUR].attributes.changedFields;
  6666. if(tCF !== undefined)
  6667. {
  6668. if(tCF.length > 0)
  6669. {
  6670. let tFN = tCF[0].attributes.fieldName;
  6671. if((tFN == "phone") && (uro_uFP[uroEnums.FP_OPTS.filterCFPhone] === true))
  6672. {
  6673. placeStyle = 'hidden';
  6674. }
  6675. else if((tFN == "name") && (uro_uFP[uroEnums.FP_OPTS.filterCFName] === true))
  6676. {
  6677. placeStyle = 'hidden';
  6678. }
  6679. else if((tFN == "entryExitPoints") && (uro_uFP[uroEnums.FP_OPTS.filterCFEntryExitPoints] === true))
  6680. {
  6681. placeStyle = 'hidden';
  6682. }
  6683. else if((tFN == "openingHours") && (uro_uFP[uroEnums.FP_OPTS.filterCFOpeningHours] === true))
  6684. {
  6685. placeStyle = 'hidden';
  6686. }
  6687. else if((tFN == "aliases") && (uro_uFP[uroEnums.FP_OPTS.filterCFAliases] === true))
  6688. {
  6689. placeStyle = 'hidden';
  6690. }
  6691. else if((tFN == "services") && (uro_uFP[uroEnums.FP_OPTS.filterCFServices] === true))
  6692. {
  6693. placeStyle = 'hidden';
  6694. }
  6695. else if((tFN == "geometry") && (uro_uFP[uroEnums.FP_OPTS.filterCFGeometry] === true))
  6696. {
  6697. placeStyle = 'hidden';
  6698. }
  6699. else if((tFN == "houseNumber") && (uro_uFP[uroEnums.FP_OPTS.filterCFHouseNumber] === true))
  6700. {
  6701. placeStyle = 'hidden';
  6702. }
  6703. else if((tFN == "categories") && (uro_uFP[uroEnums.FP_OPTS.filterCFCategories] === true))
  6704. {
  6705. placeStyle = 'hidden';
  6706. }
  6707. else if((tFN == "description") && (uro_uFP[uroEnums.FP_OPTS.filterCFDescription] === true))
  6708. {
  6709. placeStyle = 'hidden';
  6710. }
  6711. }
  6712. }
  6713. }
  6714. }
  6715. }
  6716.  
  6717. if(uro_uFP[uroEnums.FP_OPTS.invertPURFilters] === true)
  6718. {
  6719. if(placeStyle == 'hidden') placeStyle = 'visible';
  6720. else placeStyle = 'hidden';
  6721. }
  6722.  
  6723. if(uro_uFP[uroEnums.FP_OPTS.filterMinPURAge] || uro_uFP[uroEnums.FP_OPTS.filterMaxPURAge])
  6724. {
  6725. purAge = uroUtils.GetPURAge(vObj);
  6726. if(uro_uFP[uroEnums.FP_OPTS.filterMinPURAge] === true)
  6727. {
  6728. if(purAge < uro_uFP[uroEnums.FP_OPTS.thresholdMinPURDays]) placeStyle = 'hidden';
  6729. }
  6730. if(uro_uFP[uroEnums.FP_OPTS.filterMaxPURAge] === true)
  6731. {
  6732. if(purAge > uro_uFP[uroEnums.FP_OPTS.thresholdMaxPURDays]) placeStyle = 'hidden';
  6733. }
  6734. }
  6735.  
  6736. if(uroPURsToHide.indexOf(vObj.attributes.id) !== -1)
  6737. {
  6738. placeStyle = 'hidden';
  6739. }
  6740. }
  6741.  
  6742. mObj.style.visibility = placeStyle;
  6743. if(hasBalloon === true)
  6744. {
  6745. // for PURs related to multiple change requests, we also need to apply the
  6746. // filtering to the text inside the balloon that indicates how many CRs
  6747. // the PUR represents - the balloon itself gets filtered as part of the
  6748. // main marker above, but the text is in a seperate element...
  6749. let pObj = mObj.parentNode.parentNode;
  6750. let tObjs = pObj.getElementsByTagName("text");
  6751. for(let i = 0; i < tObjs.length; ++i)
  6752. {
  6753. tObjs[i].style.visibility = placeStyle;
  6754. }
  6755. }
  6756.  
  6757. if((uro_uFP[uroEnums.FP_OPTS.leavePURGeos] === false) && (placeStyle === 'hidden'))
  6758. {
  6759. if(vObj.model != null)
  6760. {
  6761. if(vObj.attributes.geometry != null)
  6762. {
  6763. let puGeo = document.getElementById(vObj.attributes.geometry.id);
  6764. if(puGeo !== null)
  6765. {
  6766. puGeo.style.visibility = 'hidden';
  6767. }
  6768. }
  6769. }
  6770. }
  6771. }
  6772. function uroFilterPlaces()
  6773. {
  6774. let pmTStart = performance.now();
  6775. let pmFunction = "uroFilterPlaces";
  6776.  
  6777. if(uroFilterPreamble() === false) return;
  6778.  
  6779. let moObj = uroGetHighlightedMapFeature();
  6780. let renderIntent = uroGetFeatureRenderIntent(moObj);
  6781. if(moObj != null)
  6782. {
  6783. if(moObj.featureType === 'venue')
  6784. {
  6785. if((renderIntent == 'select') || (renderIntent == 'highlightselected'))
  6786. {
  6787. return;
  6788. }
  6789. }
  6790. }
  6791.  
  6792. if(uroUtils.GetCBChecked('_cbDisablePlacesFiltering') === true) return;
  6793.  
  6794. uroUpdateVenueEditorLists();
  6795.  
  6796. let filterNameID = null;
  6797. let tbUserName = uroUtils.GetElmValue('_textPlacesEditor');
  6798. let selector = document.getElementById('_selectPlacesUserID');
  6799. if(selector.selectedIndex > 0)
  6800. {
  6801. let selUserName = document.getElementById('_selectPlacesUserID').selectedOptions[0].innerHTML;
  6802. if(selUserName == tbUserName)
  6803. {
  6804. filterNameID = document.getElementById('_selectPlacesUserID').selectedOptions[0].value;
  6805. }
  6806. }
  6807. filterNameID = uroGetUserID(filterNameID, tbUserName);
  6808.  
  6809. let filterHideNameID = null;
  6810. let tbHideUserName = uroUtils.GetElmValue('_textHidePlacesEditor');
  6811. let selectorHide = document.getElementById('_selectHidePlacesUserID');
  6812. if(selectorHide.selectedIndex > 0)
  6813. {
  6814. let selHideUserName = document.getElementById('_selectHidePlacesUserID').selectedOptions[0].innerHTML;
  6815. if(selHideUserName == tbHideUserName)
  6816. {
  6817. filterHideNameID = document.getElementById('_selectHidePlacesUserID').selectedOptions[0].value;
  6818. }
  6819. }
  6820. filterHideNameID = uroGetUserID(filterHideNameID, tbHideUserName);
  6821.  
  6822. let filterCats = [];
  6823. for(let i=0; i<W.Config.venues.categories.length; i++)
  6824. {
  6825. let parentCategory = W.Config.venues.categories[i];
  6826. let subCategory;
  6827.  
  6828. if(uroUtils.GetCBChecked('_cbPlacesFilter-'+parentCategory) === true)
  6829. {
  6830. filterCats.push(parentCategory);
  6831. for(let i1=0; i1<W.Config.venues.subcategories[parentCategory].length; i1++)
  6832. {
  6833. subCategory = W.Config.venues.subcategories[parentCategory][i1];
  6834. filterCats.push(subCategory);
  6835. }
  6836. }
  6837. else
  6838. {
  6839. for(let i2=0; i2<W.Config.venues.subcategories[parentCategory].length; i2++)
  6840. {
  6841. subCategory = W.Config.venues.subcategories[parentCategory][i2];
  6842. if(uroUtils.GetCBChecked('_cbPlacesFilter-'+subCategory) === true)
  6843. {
  6844. filterCats.push(subCategory);
  6845. }
  6846. }
  6847. }
  6848. }
  6849.  
  6850. let placeStyle;
  6851.  
  6852. let uFP_filterEditedLessThan = uroUtils.GetCBChecked('_cbPlaceFilterEditedLessThan');
  6853. let uFP_filterEditedMoreThan = uroUtils.GetCBChecked('_cbPlaceFilterEditedMoreThan');
  6854. let uFP_filterL0 = uroUtils.GetCBChecked('_cbHidePlacesL0');
  6855. let uFP_filterL1 = uroUtils.GetCBChecked('_cbHidePlacesL1');
  6856. let uFP_filterL2 = uroUtils.GetCBChecked('_cbHidePlacesL2');
  6857. let uFP_filterL3 = uroUtils.GetCBChecked('_cbHidePlacesL3');
  6858. let uFP_filterL4 = uroUtils.GetCBChecked('_cbHidePlacesL4');
  6859. let uFP_filterL5 = uroUtils.GetCBChecked('_cbHidePlacesL5');
  6860. let uFP_filterStaff = uroUtils.GetCBChecked('_cbHidePlacesStaff');
  6861. let uFP_filterAL = uroUtils.GetCBChecked('_cbHidePlacesAdLocked');
  6862. let uFP_filterOnLockLevel = (uFP_filterL0 || uFP_filterL1 || uFP_filterL2 || uFP_filterL3 || uFP_filterL4 || uFP_filterL5 || uFP_filterStaff);
  6863. let uFP_filterNoPhotos = uroUtils.GetCBChecked('_cbHideNoPhotoPlaces');
  6864. let uFP_filterWithPhotos = uroUtils.GetCBChecked('_cbHidePhotoPlaces');
  6865. let uFP_filterNoLinks = uroUtils.GetCBChecked('_cbHideNoLinkedPlaces');
  6866. let uFP_filterWithLinks = uroUtils.GetCBChecked('_cbHideLinkedPlaces');
  6867. let uFP_filterNoDescription = uroUtils.GetCBChecked('_cbHideNonDescribedPlaces');
  6868. let uFP_filterWithDescription = uroUtils.GetCBChecked('_cbHideDescribedPlaces');
  6869. let uFP_filterNoKeyword = uroUtils.GetCBChecked('_cbHideKeywordPlaces');
  6870. let uFP_filterKeyword = uroUtils.GetCBChecked('_cbHideNoKeywordPlaces');
  6871. let uFP_filterPrivate = uroUtils.GetCBChecked('_cbFilterPrivatePlaces');
  6872. let uFP_invertFilters = uroUtils.GetCBChecked('_cbInvertPlacesFilter');
  6873. let uFP_masterEnable = uroIsFilteringEnabled(false);
  6874. let uFP_filterAreaPlaces = uroUtils.GetCBChecked('_cbHideAreaPlaces');
  6875. let uFP_filterPointPlaces = uroUtils.GetCBChecked('_cbHidePointPlaces');
  6876. let uFP_filterCreatedBy = uroUtils.GetCBChecked('_cbShowOnlyPlacesCreatedBy');
  6877. let uFP_filterEditedBy = uroUtils.GetCBChecked('_cbShowOnlyPlacesEditedBy');
  6878. let uFP_filterHideCreatedBy = uroUtils.GetCBChecked('_cbHideOnlyPlacesCreatedBy');
  6879. let uFP_filterHideEditedBy = uroUtils.GetCBChecked('_cbHideOnlyPlacesEditedBy');
  6880.  
  6881. let uFP_hidePURsForFilteredPlaces = uroUtils.GetCBChecked('_cbHidePURsForFilteredPlaces');
  6882.  
  6883. let uFP_NameKeyword = document.getElementById('_textKeywordPlace').value.toLowerCase();
  6884. let uFP_thresholdMinDays = document.getElementById('_inputFilterPlaceEditMinDays').value;
  6885. let uFP_thresholdMaxDays = document.getElementById('_inputFilterPlaceEditMaxDays').value;
  6886.  
  6887. uroPURsToHide = [];
  6888.  
  6889. for(let v=0; v<uroVenueLayer.features.length; v++)
  6890. {
  6891. placeStyle = 'visible';
  6892. if(uFP_masterEnable === true)
  6893. {
  6894. let lmObj = uroVenueLayer.features[v];
  6895.  
  6896. // when an area place is selected, the drag points for editing the place outline now get added as objects into uroVenueLayer.features,
  6897. // however none of these objects had the .attributes.repositoryObject property - whilst the devs have now replaced this with the almost
  6898. // identical .wazeFeature._wmeObject property, it's unclear if drag points still need to be excluded from this scan, so the check
  6899. // remains in place as a "let's just make sure it has it before trying to use it"...
  6900. if(lmObj?.attributes?.wazeFeature?._wmeObject != null)
  6901. {
  6902. lmObj = lmObj.attributes.wazeFeature._wmeObject.attributes;
  6903. if(lmObj.id < 0)
  6904. {
  6905. // don't apply filtering to newly-created places - this allows the user to leave their filtering settings unchanged whilst
  6906. // adding a new place which, once saved, would then be hidden...
  6907. break;
  6908. }
  6909.  
  6910. if(uFP_filterAreaPlaces)
  6911. {
  6912. if(lmObj.geometry.id.indexOf('Polygon') !== -1)
  6913. {
  6914. placeStyle = 'hidden';
  6915. }
  6916. }
  6917. if(uFP_filterPointPlaces)
  6918. {
  6919. if(lmObj.geometry.id.indexOf('Point') !== -1)
  6920. {
  6921. placeStyle = 'hidden';
  6922. }
  6923. }
  6924.  
  6925.  
  6926. if(placeStyle == 'visible')
  6927. {
  6928. if((uFP_filterEditedLessThan) || (uFP_filterEditedMoreThan))
  6929. {
  6930. let editDate = lmObj.updatedOn;
  6931. if(editDate === undefined)
  6932. {
  6933. // where a place has never been edited since its creation, use the creation date instead...
  6934. editDate = lmObj.createdOn;
  6935. }
  6936. if(editDate != null)
  6937. {
  6938. let editDaysAgo = uroUtils.DateToDays(editDate);
  6939. if(uFP_filterEditedLessThan)
  6940. {
  6941. if(editDaysAgo < uFP_thresholdMinDays)
  6942. {
  6943. placeStyle = 'hidden';
  6944. }
  6945. }
  6946. if(uFP_filterEditedMoreThan)
  6947. {
  6948. if(editDaysAgo > uFP_thresholdMaxDays)
  6949. {
  6950. placeStyle = 'hidden';
  6951. }
  6952. }
  6953. }
  6954. }
  6955. }
  6956.  
  6957. if(placeStyle == 'visible')
  6958. {
  6959. if(uFP_filterOnLockLevel)
  6960. {
  6961. let lockLevel = lmObj.lockRank;
  6962. if ((uFP_filterL0) && (lockLevel === 0)) placeStyle = 'hidden';
  6963. if ((uFP_filterL1) && (lockLevel === 1)) placeStyle = 'hidden';
  6964. if ((uFP_filterL2) && (lockLevel === 2)) placeStyle = 'hidden';
  6965. if ((uFP_filterL3) && (lockLevel === 3)) placeStyle = 'hidden';
  6966. if ((uFP_filterL4) && (lockLevel === 4)) placeStyle = 'hidden';
  6967. if ((uFP_filterL5) && (lockLevel === 5)) placeStyle = 'hidden';
  6968. if ((uFP_filterStaff) && (lockLevel === 6)) placeStyle = 'hidden';
  6969. }
  6970. }
  6971.  
  6972. if(placeStyle == 'visible')
  6973. {
  6974. if(uFP_filterAL)
  6975. {
  6976. if(lmObj.adLocked) placeStyle = 'hidden';
  6977. }
  6978. }
  6979.  
  6980. if(placeStyle == 'visible')
  6981. {
  6982. if(uFP_filterNoPhotos || uFP_filterWithPhotos)
  6983. {
  6984. let nPhotos = 0;
  6985. for(let loop=0; loop<lmObj.images.length; loop++)
  6986. {
  6987. if(lmObj.images[loop].attributes.approved) nPhotos++;
  6988. }
  6989. if((uFP_filterNoPhotos) && (nPhotos === 0)) placeStyle = 'hidden';
  6990. if((uFP_filterWithPhotos) && (nPhotos !== 0)) placeStyle = 'hidden';
  6991. }
  6992. }
  6993.  
  6994. if(placeStyle == 'visible')
  6995. {
  6996. if(uFP_filterNoLinks || uFP_filterWithLinks)
  6997. {
  6998. let nLinks = lmObj.externalProviderIDs.length;
  6999. if((uFP_filterNoLinks) && (nLinks === 0)) placeStyle = 'hidden';
  7000. if((uFP_filterWithLinks) && (nLinks !== 0)) placeStyle = 'hidden';
  7001. }
  7002. }
  7003.  
  7004. if(placeStyle == 'visible')
  7005. {
  7006. if(uFP_filterNoDescription || uFP_filterWithDescription)
  7007. {
  7008. let lDesc = lmObj.description.length;
  7009. if((uFP_filterNoDescription) && (lDesc === 0)) placeStyle = 'hidden';
  7010. if((uFP_filterWithDescription) && (lDesc !== 0)) placeStyle = 'hidden';
  7011. }
  7012. }
  7013.  
  7014. if(placeStyle == 'visible')
  7015. {
  7016. if((uFP_filterPrivate === true) && (lmObj.residential === true))
  7017. {
  7018. placeStyle = 'hidden';
  7019. }
  7020. else
  7021. {
  7022. for(let cat=0; cat<filterCats.length; cat++)
  7023. {
  7024. if(_.includes(lmObj.categories, filterCats[cat]))
  7025. {
  7026. placeStyle = 'hidden';
  7027. break;
  7028. }
  7029. }
  7030. }
  7031. }
  7032.  
  7033. if(placeStyle == 'visible')
  7034. {
  7035. if(uFP_filterNoKeyword || uFP_filterKeyword)
  7036. {
  7037. let venueName = lmObj.name.toLowerCase();
  7038. let noKeywordMatch = true;
  7039. if(uFP_NameKeyword === '')
  7040. {
  7041. noKeywordMatch = (venueName !== '');
  7042. }
  7043. else
  7044. {
  7045. noKeywordMatch = (venueName.indexOf(uFP_NameKeyword) === -1);
  7046. }
  7047.  
  7048. if(!noKeywordMatch && uFP_filterNoKeyword) placeStyle = 'hidden';
  7049. if(noKeywordMatch && uFP_filterKeyword) placeStyle = 'hidden';
  7050. }
  7051. }
  7052.  
  7053. if(placeStyle == 'visible')
  7054. {
  7055. if(filterNameID != null)
  7056. {
  7057. if(uFP_filterCreatedBy === true)
  7058. {
  7059. if(filterNameID != lmObj.createdBy) placeStyle = 'hidden';
  7060. }
  7061. if(uFP_filterEditedBy === true)
  7062. {
  7063. if(filterNameID != lmObj.updatedBy) placeStyle = 'hidden';
  7064. }
  7065. }
  7066. }
  7067. if(placeStyle == 'visible')
  7068. {
  7069. if(filterHideNameID != null)
  7070. {
  7071. if(uFP_filterHideCreatedBy === true)
  7072. {
  7073. if(filterHideNameID == lmObj.createdBy) placeStyle = 'hidden';
  7074. }
  7075. if(uFP_filterHideEditedBy === true)
  7076. {
  7077. if(filterHideNameID == lmObj.updatedBy) placeStyle = 'hidden';
  7078. }
  7079. }
  7080. }
  7081.  
  7082. if(uFP_invertFilters === true)
  7083. {
  7084. if(placeStyle == 'hidden') placeStyle = 'visible';
  7085. else placeStyle = 'hidden';
  7086. }
  7087. }
  7088.  
  7089. if((placeStyle == 'hidden') && (uFP_hidePURsForFilteredPlaces === true))
  7090. {
  7091. uroPURsToHide.push(lmObj.id);
  7092. }
  7093. }
  7094.  
  7095. let geoID = uroVenueLayer.features[v].geometry.id;
  7096. if(document.getElementById(geoID) !== null)
  7097. {
  7098. document.getElementById(geoID).style.visibility = placeStyle;
  7099. }
  7100. }
  7101.  
  7102. uro_uFP[uroEnums.FP_OPTS.filterUneditable] = uroUtils.GetCBChecked('_cbFilterUneditablePlaceUpdates');
  7103. uro_uFP[uroEnums.FP_OPTS.filterInsideManagedAreas] = uroUtils.GetCBChecked('_cbPURFilterInsideManagedAreas');
  7104. uro_uFP[uroEnums.FP_OPTS.excludeMyAreas] = uroUtils.GetCBChecked('_cbPURExcludeUserArea');
  7105. uro_uFP[uroEnums.FP_OPTS.filterLockRanked] = uroUtils.GetCBChecked('_cbFilterLockRankedPlaceUpdates');
  7106. uro_uFP[uroEnums.FP_OPTS.filterFlagged] = uroUtils.GetCBChecked("_cbFilterFlaggedPUR");
  7107. uro_uFP[uroEnums.FP_OPTS.filterNewPlace] = uroUtils.GetCBChecked("_cbFilterNewPlacePUR");
  7108. uro_uFP[uroEnums.FP_OPTS.filterUpdatedDetails] = uroUtils.GetCBChecked("_cbFilterUpdatedDetailsPUR");
  7109. uro_uFP[uroEnums.FP_OPTS.filterNewPhoto] = uroUtils.GetCBChecked("_cbFilterNewPhotoPUR");
  7110. uro_uFP[uroEnums.FP_OPTS.filterMinPURAge] = uroUtils.GetCBChecked('_cbEnablePURMinAgeFilter');
  7111. uro_uFP[uroEnums.FP_OPTS.filterMaxPURAge] = uroUtils.GetCBChecked('_cbEnablePURMaxAgeFilter');
  7112. uro_uFP[uroEnums.FP_OPTS.invertPURFilters] = uroUtils.GetCBChecked('_cbInvertPURFilters');
  7113. uro_uFP[uroEnums.FP_OPTS.leavePURGeos] = uroUtils.GetCBChecked('_cbLeavePURGeos');
  7114. uro_uFP[uroEnums.FP_OPTS.filterCFPhone] = uroUtils.GetCBChecked('_cbPURFilterCFPhone');
  7115. uro_uFP[uroEnums.FP_OPTS.filterCFName] = uroUtils.GetCBChecked('_cbPURFilterCFName');
  7116. uro_uFP[uroEnums.FP_OPTS.filterCFEntryExitPoints] = uroUtils.GetCBChecked('_cbPURFilterCFEntryExitPoints');
  7117. uro_uFP[uroEnums.FP_OPTS.filterCFOpeningHours] = uroUtils.GetCBChecked('_cbPURFilterCFOpeningHours');
  7118. uro_uFP[uroEnums.FP_OPTS.filterCFAliases] = uroUtils.GetCBChecked('_cbPURFilterCFAliases');
  7119. uro_uFP[uroEnums.FP_OPTS.filterCFServices] = uroUtils.GetCBChecked('_cbPURFilterCFServices');
  7120. uro_uFP[uroEnums.FP_OPTS.filterCFGeometry] = uroUtils.GetCBChecked('_cbPURFilterCFGeometry');
  7121. uro_uFP[uroEnums.FP_OPTS.filterCFHouseNumber] = uroUtils.GetCBChecked('_cbPURFilterCFHouseNumber');
  7122. uro_uFP[uroEnums.FP_OPTS.filterCFCategories] = uroUtils.GetCBChecked('_cbPURFilterCFCategories');
  7123. uro_uFP[uroEnums.FP_OPTS.filterCFDescription] = uroUtils.GetCBChecked('_cbPURFilterCFDescription');
  7124.  
  7125. uro_uFP[uroEnums.FP_OPTS.filterOnCFs] =
  7126. (
  7127. uro_uFP[uroEnums.FP_OPTS.filterCFPhone] ||
  7128. uro_uFP[uroEnums.FP_OPTS.filterCFName] ||
  7129. uro_uFP[uroEnums.FP_OPTS.filterCFEntryExitPoints] ||
  7130. uro_uFP[uroEnums.FP_OPTS.filterCFOpeningHours]
  7131. );
  7132. uro_uFP[uroEnums.FP_OPTS.filterOnCFs] =
  7133. (
  7134. uro_uFP[uroEnums.FP_OPTS.filterOnCFs] ||
  7135. uro_uFP[uroEnums.FP_OPTS.filterCFAliases] ||
  7136. uro_uFP[uroEnums.FP_OPTS.filterCFServices] ||
  7137. uro_uFP[uroEnums.FP_OPTS.filterCFGeometry]
  7138. );
  7139. uro_uFP[uroEnums.FP_OPTS.filterOnCFs] =
  7140. (
  7141. uro_uFP[uroEnums.FP_OPTS.filterOnCFs] ||
  7142. uro_uFP[uroEnums.FP_OPTS.filterCFHouseNumber] ||
  7143. uro_uFP[uroEnums.FP_OPTS.filterCFCategories] ||
  7144. uro_uFP[uroEnums.FP_OPTS.filterCFDescription]
  7145. );
  7146.  
  7147. uro_uFP[uroEnums.FP_OPTS.thresholdMinPURDays] = uroUtils.GetElmValue('_inputPURFilterMinDays');
  7148. uro_uFP[uroEnums.FP_OPTS.thresholdMaxPURDays] = uroUtils.GetElmValue('_inputPURFilterMaxDays');
  7149. uro_uFP[uroEnums.FP_OPTS.isLoggedIn] = W.loginManager.isLoggedIn();
  7150. uro_uFP[uroEnums.FP_OPTS.userRank] = W.loginManager.user.attributes.rank;
  7151.  
  7152. uro_uFP[uroEnums.FP_OPTS.filterInsideManagedAreas] = uro_uFP[uroEnums.FP_OPTS.filterInsideManagedAreas] && (uroGetManagedAreas() !== 0);
  7153. if(uroUtils.GetCBChecked('_cbPURExcludeUserArea') == true)
  7154. {
  7155. uroIgnoreAreasUserID = W.loginManager.user.attributes.id;
  7156. }
  7157.  
  7158. uroPrepForFilterPlaceMarker(uroLayers.ID.PUR, uFP_masterEnable);
  7159. uroPrepForFilterPlaceMarker(uroLayers.ID.PPUR, uFP_masterEnable);
  7160. uroPrepForFilterPlaceMarker(uroLayers.ID.RPUR, uFP_masterEnable);
  7161.  
  7162. uroDBG.PerfMon(pmFunction, pmTStart);
  7163. }
  7164. function uroPrepForFilterPlaceMarker(markerType, masterEnable)
  7165. {
  7166. if(uroLayers.layers[markerType].l?.getVisibility() === true)
  7167. {
  7168. let pu;
  7169. let mObj;
  7170. let vObj;
  7171. let idList = uroGetMarkerIDs(markerType);
  7172. for(pu of idList)
  7173. {
  7174. mObj = uroGetMarker(markerType, pu);
  7175. if(mObj !== null)
  7176. {
  7177. vObj = W.model.venues.objects[pu];
  7178. uroFilterPlaceMarker(mObj, vObj, masterEnable);
  7179. }
  7180. }
  7181. }
  7182. }
  7183. function uroGetClosestSegmentToPoint(p)
  7184. {
  7185. let retval = null;
  7186. if(W.map.getZoom() >= 16)
  7187. {
  7188. let minDist = 99999999;
  7189.  
  7190. for(let s in W.model.segments.objects)
  7191. {
  7192. if(W.model.segments.objects.hasOwnProperty(s))
  7193. {
  7194. let seg = W.model.segments.getObjectById(s);
  7195. let dist = seg.attributes.geometry.distanceTo(p);
  7196. if(dist < minDist)
  7197. {
  7198. minDist = dist;
  7199. retval = s;
  7200. }
  7201. }
  7202. }
  7203. }
  7204. return retval;
  7205. }
  7206. function uroIsCamSpeedValid(camObj)
  7207. {
  7208. let retval = true;
  7209.  
  7210. let cPoint = camObj.attributes.geometry.getCentroid();
  7211. let nSeg = uroGetClosestSegmentToPoint(cPoint);
  7212. if(nSeg !== null)
  7213. {
  7214. let fwdSpeed = W.model.segments.getObjectById(nSeg).attributes.fwdMaxSpeed;
  7215. let revSpeed = W.model.segments.getObjectById(nSeg).attributes.revMaxSpeed;
  7216. let camSpeed = camObj.attributes.speed;
  7217. if(W.model.isImperial == true)
  7218. {
  7219. fwdSpeed = Math.round(fwdSpeed / 1.609);
  7220. revSpeed = Math.round(revSpeed / 1.609);
  7221. camSpeed = Math.round(camSpeed / 1.609);
  7222. }
  7223. if((camSpeed !== fwdSpeed) && (camSpeed !== revSpeed))
  7224. {
  7225. retval = false;
  7226. }
  7227. }
  7228.  
  7229. return retval;
  7230. }
  7231. function uroFilterCameras()
  7232. {
  7233. let pmTStart = performance.now();
  7234. let pmFunction = "uroFilterCameras";
  7235.  
  7236. if(uroFilterPreamble() === false)
  7237. {
  7238. return;
  7239. }
  7240.  
  7241. if(uroMouseIsDown === false) W.map.camerasLayer.redraw();
  7242.  
  7243. if(uroIsFilteringEnabled(false) === true)
  7244. {
  7245. uroUpdateEditorList(W.model.cameras.objects, '_selectCameraUserID', true, true, false, false);
  7246. let tbUserName = uroUtils.GetElmValue('_textCameraEditor');
  7247. let selector = document.getElementById('_selectCameraUserID');
  7248. let filterNameID = null;
  7249. if(selector.selectedIndex > 0)
  7250. {
  7251. let selUserName = document.getElementById('_selectCameraUserID').selectedOptions[0].innerHTML;
  7252. if(selUserName == tbUserName)
  7253. {
  7254. filterNameID = document.getElementById('_selectCameraUserID').selectedOptions[0].value;
  7255. }
  7256. }
  7257. filterNameID = uroGetUserID(filterNameID, tbUserName);
  7258.  
  7259. let isChecked_cbShowOnlyCamsCreatedBy = uroUtils.GetCBChecked('_cbShowOnlyCamsCreatedBy');
  7260. let isChecked_cbShowOnlyCamsEditedBy = uroUtils.GetCBChecked('_cbShowOnlyCamsEditedBy');
  7261. let isChecked_cbShowOnlyMyCams = uroUtils.GetCBChecked('_cbShowOnlyMyCams');
  7262. let isChecked_cbShowWorldCams = uroUtils.GetCBChecked('_cbShowWorldCams');
  7263. let isChecked_cbShowUSACams = uroUtils.GetCBChecked('_cbShowUSACams');
  7264. let isChecked_cbShowNonWorldCams = uroUtils.GetCBChecked('_cbShowNonWorldCams');
  7265. let isChecked_cbShowSpeedCams = uroUtils.GetCBChecked('_cbShowSpeedCams');
  7266. let isChecked_cbShowRedLightCams = uroUtils.GetCBChecked('_cbShowRedLightCams');
  7267. let isChecked_cbShowDummyCams = uroUtils.GetCBChecked('_cbShowDummyCams');
  7268. let isChecked_cbShowIfNoSpeedSet = uroUtils.GetCBChecked('_cbShowIfNoSpeedSet');
  7269. let isChecked_cbShowIfSpeedSet = uroUtils.GetCBChecked('_cbShowIfSpeedSet');
  7270. let isChecked_cbShowIfInvalidSpeedSet = uroUtils.GetCBChecked('_cbShowIfInvalidSpeedSet');
  7271. let isChecked_cbShowRLCIfNoSpeedSet = uroUtils.GetCBChecked('_cbShowRLCIfNoSpeedSet');
  7272. let isChecked_cbShowRLCIfNonZeroSpeedSet = uroUtils.GetCBChecked('_cbShowRLCIfNonZeroSpeedSet');
  7273. let isChecked_cbShowRLCIfZeroSpeedSet = uroUtils.GetCBChecked('_cbShowRLCIfZeroSpeedSet');
  7274. let isChecked_cbHideCreatedByMe = uroUtils.GetCBChecked('_cbHideCreatedByMe');
  7275. let isChecked_cbHideCreatedByRank0 = uroUtils.GetCBChecked('_cbHideCreatedByRank0');
  7276. let isChecked_cbHideCreatedByRank1 = uroUtils.GetCBChecked('_cbHideCreatedByRank1');
  7277. let isChecked_cbHideCreatedByRank2 = uroUtils.GetCBChecked('_cbHideCreatedByRank2');
  7278. let isChecked_cbHideCreatedByRank3 = uroUtils.GetCBChecked('_cbHideCreatedByRank3');
  7279. let isChecked_cbHideCreatedByRank4 = uroUtils.GetCBChecked('_cbHideCreatedByRank4');
  7280. let isChecked_cbHideCreatedByRank5 = uroUtils.GetCBChecked('_cbHideCreatedByRank5');
  7281. let isChecked_cbHideUpdatedByMe = uroUtils.GetCBChecked('_cbHideUpdatedByMe');
  7282. let isChecked_cbHideUpdatedByRank0 = uroUtils.GetCBChecked('_cbHideUpdatedByRank0');
  7283. let isChecked_cbHideUpdatedByRank1 = uroUtils.GetCBChecked('_cbHideUpdatedByRank1');
  7284. let isChecked_cbHideUpdatedByRank2 = uroUtils.GetCBChecked('_cbHideUpdatedByRank2');
  7285. let isChecked_cbHideUpdatedByRank3 = uroUtils.GetCBChecked('_cbHideUpdatedByRank3');
  7286. let isChecked_cbHideUpdatedByRank4 = uroUtils.GetCBChecked('_cbHideUpdatedByRank4');
  7287. let isChecked_cbHideUpdatedByRank5 = uroUtils.GetCBChecked('_cbHideUpdatedByRank5');
  7288. let isChecked_HideManualLockedCams = uroUtils.GetCBChecked('_cbHideManualLockedCams');
  7289. let isChecked_cbHideCWLCams = uroUtils.GetCBChecked('_cbHideCWLCams');
  7290. let isChecked_cbHighlightInsteadOfHideCams = uroUtils.GetCBChecked('_cbHighlightInsteadOfHideCams');
  7291. let isChecked_InvertFiltere = uroUtils.GetCBChecked('_cbInvertCamFilters');
  7292.  
  7293. let nCameras = uroLayers.layers[uroLayers.ID.cam].l.features.length;
  7294. for (let i = 0; i < nCameras; ++i)
  7295. {
  7296. let uroCamUpdater = '';
  7297. let uroCamUpdaterRank = -1;
  7298. let uroCamCreator = '';
  7299. let uroCamCreatorRank = -1;
  7300. let wf = uroLayers.layers[uroLayers.ID.cam].l.features[i].attributes.wazeFeature;
  7301. // When a camera is selected, the alignment/positioning UI elements get added to features[].
  7302. // As these elements aren't camera markers and therefore have no attributes, we need to
  7303. // ignore them to prevent errors in the filtering code below...
  7304. if(wf !== undefined)
  7305. {
  7306. let uroCam = wf._wmeObject;
  7307. let uroCamStyle = 'visible';
  7308.  
  7309. if(uroCam.attributes.createdBy !== null)
  7310. {
  7311. if(W.model.users.objects[uroCam.attributes.createdBy] != null)
  7312. {
  7313. uroCamCreator = W.model.users.objects[uroCam.attributes.createdBy].attributes.userName;
  7314. uroCamCreatorRank = W.model.users.objects[uroCam.attributes.createdBy].attributes.rank;
  7315. }
  7316. }
  7317.  
  7318. if(uroCam.attributes.updatedBy !== null)
  7319. {
  7320. if(W.model.users.objects[uroCam.attributes.updatedBy] != null)
  7321. {
  7322. uroCamUpdater = W.model.users.objects[uroCam.attributes.updatedBy].attributes.userName;
  7323. uroCamUpdaterRank = W.model.users.objects[uroCam.attributes.updatedBy].attributes.rank;
  7324. }
  7325. }
  7326.  
  7327. let uroCamType = uroCam.attributes.type;
  7328. let camIsAutoLocked = (uroCam.attributes.lockRank === null);
  7329.  
  7330. if(isChecked_HideManualLockedCams === true)
  7331. {
  7332. if(camIsAutoLocked === false) uroCamStyle = 'hidden';
  7333. }
  7334.  
  7335. if(filterNameID != null)
  7336. {
  7337. if(isChecked_cbShowOnlyCamsCreatedBy === true)
  7338. {
  7339. if(filterNameID != uroCam.attributes.createdBy) uroCamStyle = 'hidden';
  7340. }
  7341. if(isChecked_cbShowOnlyCamsEditedBy === true)
  7342. {
  7343. if(filterNameID != uroCam.attributes.updatedBy) uroCamStyle = 'hidden';
  7344. }
  7345. }
  7346.  
  7347. if(isChecked_cbShowOnlyMyCams === true)
  7348. {
  7349. if((uroUserID != uroCam.attributes.createdBy)&&(uroUserID != uroCam.attributes.updatedBy)) uroCamStyle = 'hidden';
  7350. }
  7351.  
  7352. if((isChecked_cbShowWorldCams === false) || (isChecked_cbShowUSACams === false) || (isChecked_cbShowNonWorldCams === false))
  7353. {
  7354. let posWorld = uroCamCreator.indexOf('world_');
  7355. let posUSA = uroCamCreator.indexOf('usa_');
  7356.  
  7357. if((isChecked_cbShowWorldCams === false) && (posWorld === 0)) uroCamStyle = 'hidden';
  7358. if((isChecked_cbShowUSACams === false) && (posUSA === 0)) uroCamStyle = 'hidden';
  7359. if((isChecked_cbShowNonWorldCams === false) && (posWorld !== 0) && (posUSA !== 0)) uroCamStyle = 'hidden';
  7360. }
  7361.  
  7362. if((isChecked_cbShowSpeedCams === false) || (isChecked_cbShowRedLightCams === false) || (isChecked_cbShowDummyCams === false))
  7363. {
  7364. if((isChecked_cbShowSpeedCams === false) && (uroCamType == 2)) uroCamStyle = 'hidden';
  7365. if((isChecked_cbShowRedLightCams === false) && (uroCamType == 4)) uroCamStyle = 'hidden';
  7366. if((isChecked_cbShowDummyCams === false) && (uroCamType == 3)) uroCamStyle = 'hidden';
  7367. }
  7368.  
  7369. if((isChecked_cbShowSpeedCams === true) && (uroCamType == 2))
  7370. {
  7371. if((isChecked_cbShowIfNoSpeedSet === false) && (uroCam.attributes.speed === null)) uroCamStyle = 'hidden';
  7372. if((isChecked_cbShowIfSpeedSet === false) && (uroCam.attributes.speed !== null)) uroCamStyle = 'hidden';
  7373. if(isChecked_cbShowIfInvalidSpeedSet === false)
  7374. {
  7375. if(uroIsCamSpeedValid(uroCam) === false)
  7376. {
  7377. uroCamStyle = 'hidden';
  7378. }
  7379. }
  7380. }
  7381.  
  7382. if((isChecked_cbShowRedLightCams === true) && (uroCamType == 4))
  7383. {
  7384. if((isChecked_cbShowRLCIfNoSpeedSet === false) && (uroCam.attributes.speed === null)) uroCamStyle = 'hidden';
  7385. if((isChecked_cbShowRLCIfNonZeroSpeedSet === false) && (uroCam.attributes.speed > 0)) uroCamStyle = 'hidden';
  7386. if((isChecked_cbShowRLCIfZeroSpeedSet === false) && (uroCam.attributes.speed === 0)) uroCamStyle = 'hidden';
  7387. }
  7388.  
  7389. if(isChecked_cbHideCreatedByMe === true)
  7390. {
  7391. if(uroUserID == uroCam.attributes.createdBy) uroCamStyle = 'hidden';
  7392. }
  7393. if((isChecked_cbHideCreatedByRank0 === true) && (uroCamCreatorRank === 0)) uroCamStyle = 'hidden';
  7394. if((isChecked_cbHideCreatedByRank1 === true) && (uroCamCreatorRank == 1)) uroCamStyle = 'hidden';
  7395. if((isChecked_cbHideCreatedByRank2 === true) && (uroCamCreatorRank == 2)) uroCamStyle = 'hidden';
  7396. if((isChecked_cbHideCreatedByRank3 === true) && (uroCamCreatorRank == 3)) uroCamStyle = 'hidden';
  7397. if((isChecked_cbHideCreatedByRank4 === true) && (uroCamCreatorRank == 4)) uroCamStyle = 'hidden';
  7398. if((isChecked_cbHideCreatedByRank5 === true) && (uroCamCreatorRank == 5)) uroCamStyle = 'hidden';
  7399.  
  7400. if(isChecked_cbHideUpdatedByMe === true)
  7401. {
  7402. if(uroUserID == uroCam.attributes.updatedBy) uroCamStyle = 'hidden';
  7403. }
  7404. if((isChecked_cbHideUpdatedByRank0 === true) && (uroCamUpdaterRank === 0)) uroCamStyle = 'hidden';
  7405. if((isChecked_cbHideUpdatedByRank1 === true) && (uroCamUpdaterRank == 1)) uroCamStyle = 'hidden';
  7406. if((isChecked_cbHideUpdatedByRank2 === true) && (uroCamUpdaterRank == 2)) uroCamStyle = 'hidden';
  7407. if((isChecked_cbHideUpdatedByRank3 === true) && (uroCamUpdaterRank == 3)) uroCamStyle = 'hidden';
  7408. if((isChecked_cbHideUpdatedByRank4 === true) && (uroCamUpdaterRank == 4)) uroCamStyle = 'hidden';
  7409. if((isChecked_cbHideUpdatedByRank5 === true) && (uroCamUpdaterRank == 5)) uroCamStyle = 'hidden';
  7410.  
  7411. if((isChecked_cbHideCWLCams === true) && (uroOWL.IsCamOnWatchList(uroCam.attributes.id) != -1)) uroCamStyle = 'hidden';
  7412.  
  7413. if(isChecked_InvertFiltere === true)
  7414. {
  7415. if(uroCamStyle == "hidden")
  7416. {
  7417. uroCamStyle = "";
  7418. }
  7419. else
  7420. {
  7421. uroCamStyle = "hidden";
  7422. }
  7423. }
  7424.  
  7425. let uroCamGeometryID = uroLayers.layers[uroLayers.ID.cam].l.features[i].geometry.id;
  7426. let svgElm = document.getElementById(uroCamGeometryID);
  7427.  
  7428. if(svgElm !== null)
  7429. {
  7430. let origImage;
  7431. if(uroCamStyle == "hidden")
  7432. {
  7433. if(isChecked_cbHighlightInsteadOfHideCams === true)
  7434. {
  7435. // set the "highlight" camera image here...
  7436. let hrefImage = svgElm.getAttribute("xlink:href");
  7437. origImage = svgElm.getAttribute("origImage");
  7438. if((hrefImage === origImage) || (origImage === null))
  7439. {
  7440. svgElm.setAttribute("origImage", hrefImage);
  7441. svgElm.setAttribute("xlink:href", uroImages.HighlightedCameraImages[(uroCamType-2)]);
  7442.  
  7443. svgElm.addEventListener("mouseover", uroMarkers.MouseOver, false);
  7444. svgElm.addEventListener("mouseout", uroMarkers.MouseOut, false);
  7445. }
  7446. }
  7447. else
  7448. {
  7449. svgElm.remove();
  7450. }
  7451. }
  7452. else
  7453. {
  7454. // restore the original camera image here...
  7455. if(svgElm.getAttribute("origImage") !== null)
  7456. {
  7457. origImage = svgElm.getAttribute("origImage");
  7458. svgElm.setAttribute("xlink:href", origImage);
  7459. svgElm.removeAttribute("origImage");
  7460. }
  7461. }
  7462. }
  7463. }
  7464. }
  7465. }
  7466. uroDBG.PerfMon(pmFunction, pmTStart);
  7467. }
  7468. function uroFilterMapComments()
  7469. {
  7470. let pmTStart = performance.now();
  7471. let pmFunction = "uroFilterMapComments";
  7472.  
  7473. if(uroFilterPreamble() === false) return;
  7474.  
  7475. let uFURs_masterEnable = uroIsFilteringEnabled(false);
  7476. let filterDescMustBePresent = uroUtils.GetCBChecked('_cbMCDescriptionMustBePresent');
  7477. let filterDescMustBeAbsent = uroUtils.GetCBChecked('_cbMCDescriptionMustBeAbsent');
  7478. let filterKeywordMustBePresent = uroUtils.GetCBChecked('_cbMCEnableKeywordMustBePresent');
  7479. let filterKeywordMustBeAbsent = uroUtils.GetCBChecked('_cbMCEnableKeywordMustBeAbsent');
  7480. let filterMyFollowed = uroUtils.GetCBChecked('_cbMCHideMyFollowed');
  7481. let filterMyUnfollowed = uroUtils.GetCBChecked('_cbMCHideMyUnfollowed');
  7482. let filterRoadworks = uroUtils.GetCBChecked('_cbMCFilterRoadworks');
  7483. let filterConstruction = uroUtils.GetCBChecked('_cbMCFilterConstruction');
  7484. let filterClosure = uroUtils.GetCBChecked('_cbMCFilterClosure');
  7485. let filterEvent = uroUtils.GetCBChecked('_cbMCFilterEvent');
  7486. let filterNote = uroUtils.GetCBChecked('_cbMCFilterNote');
  7487. let filterWSLM = uroUtils.GetCBChecked('_cbMCFilterWSLM');
  7488. let filterBOG = uroUtils.GetCBChecked('_cbMCFilterBOG');
  7489. let filterDifficult = uroUtils.GetCBChecked('_cbMCFilterDifficult');
  7490. let invertFilters = uroUtils.GetCBChecked('_cbInvertMCFilter');
  7491. let keywordPresent = uroUtils.GetElmValue('_textMCKeywordPresent');
  7492. let keywordAbsent = uroUtils.GetElmValue('_textMCKeywordAbsent');
  7493. let caseInsensitive = uroUtils.GetCBChecked('_cbMCCaseInsensitive');
  7494. let filterCommentsMustBePresent = uroUtils.GetCBChecked('_cbMCCommentsMustBePresent');
  7495. let filterCommentsMustBeAbsent = uroUtils.GetCBChecked('_cbMCCommentsMustBeAbsent');
  7496.  
  7497. let filterExpiryMustBePresent = uroUtils.GetCBChecked('_cbMCExpiryMustBePresent');
  7498. let filterExpiryMustBeAbsent = uroUtils.GetCBChecked('_cbMCExpiryMustBeAbsent');
  7499. let filterByCreatorEnable = uroUtils.GetCBChecked('_cbMCCreatorIDFilter');
  7500. let filterL1 = uroUtils.GetCBChecked('_cbHideMCRank0');
  7501. let filterL2 = uroUtils.GetCBChecked('_cbHideMCRank1');
  7502. let filterL3 = uroUtils.GetCBChecked('_cbHideMCRank2');
  7503. let filterL4 = uroUtils.GetCBChecked('_cbHideMCRank3');
  7504. let filterL5 = uroUtils.GetCBChecked('_cbHideMCRank4');
  7505. let filterL6 = uroUtils.GetCBChecked('_cbHideMCRank5');
  7506. let filterWRCMC = uroUtils.GetCBChecked('_cbHideWRCMCs');
  7507. let selectorCreator = document.getElementById('_selectMCCreatorID');
  7508.  
  7509. if(filterByCreatorEnable === false)
  7510. {
  7511. while(selectorCreator.options.length > 0)
  7512. {
  7513. selectorCreator.options.remove(0);
  7514. }
  7515. }
  7516. let creatorUser = null;
  7517. if(filterByCreatorEnable === true)
  7518. {
  7519. if(selectorCreator.options.length === 0)
  7520. {
  7521. uroUpdateEditorList(W.model.mapComments.objects, '_selectMCCreatorID', true, false, false, false);
  7522. }
  7523. if(selectorCreator.selectedOptions[0] != null)
  7524. {
  7525. creatorUser = parseInt(selectorCreator.selectedOptions[0].value);
  7526. }
  7527. }
  7528.  
  7529. for (let mcIdx = 0; mcIdx < uroMCLayer.features.length; mcIdx++)
  7530. {
  7531. {
  7532. let mcObj = uroMCLayer?.features[mcIdx]?.attributes?.wazeFeature?._wmeObject;
  7533. if(mcObj !== undefined)
  7534. {
  7535. let desc = '';
  7536. if(mcObj.attributes.subject !== null) desc += mcObj.attributes.subject.replace(/<\/?[^>]+(>|$)/g, "");
  7537. if(mcObj.attributes.body !== null) desc += mcObj.attributes.body.replace(/<\/?[^>]+(>|$)/g, "");
  7538. let nComments = mcObj.attributes.conversation.length;
  7539. if(nComments > 0)
  7540. {
  7541. for(let cIdx=0; cIdx < nComments; cIdx++)
  7542. {
  7543. desc += mcObj.attributes.conversation[cIdx].text.replace(/<\/?[^>]+(>|$)/g, "");
  7544. }
  7545. }
  7546.  
  7547. let mcStyle = 'visible';
  7548. if(uroIgnore.IsOnList(mcObj.attributes.id)) mcStyle = 'hidden';
  7549. if(uFURs_masterEnable === true)
  7550. {
  7551. let ukroadworks_ur = false;
  7552. let construction_ur = false;
  7553. let closure_ur = false;
  7554. let event_ur = false;
  7555. let note_ur = false;
  7556. let wslm_ur = false;
  7557. let bog_ur = false;
  7558. let difficult_ur = false;
  7559.  
  7560. let filterByNotIncludedKeyword = false;
  7561. let filterByIncludedKeyword = true;
  7562.  
  7563. let customType = uroGetCustomType(null, "mc", desc);
  7564. if(customType === 0) ukroadworks_ur = true;
  7565. else if(customType === 1) construction_ur = true;
  7566. else if(customType === 2) closure_ur = true;
  7567. else if(customType === 3) event_ur = true;
  7568. else if(customType === 4) note_ur = true;
  7569. else if(customType === 5) wslm_ur = true;
  7570. else if(customType === 6) bog_ur = true;
  7571. else if(customType === 7) difficult_ur = true;
  7572.  
  7573. let rank = mcObj.attributes.lockRank;
  7574. let expiry = mcObj.attributes.endDate;
  7575.  
  7576. // keywords
  7577. if(mcStyle == 'visible')
  7578. {
  7579. if(filterDescMustBePresent === true)
  7580. {
  7581. if(desc === '') mcStyle = 'hidden';
  7582. }
  7583. if(filterDescMustBeAbsent === true)
  7584. {
  7585. if(desc !== '') mcStyle = 'hidden';
  7586. }
  7587.  
  7588. if(filterCommentsMustBePresent === true)
  7589. {
  7590. if(nComments === 0) mcStyle = 'hidden';
  7591. }
  7592. if(filterCommentsMustBeAbsent === true)
  7593. {
  7594. if(nComments > 0) mcStyle = 'hidden';
  7595. }
  7596.  
  7597. if(filterKeywordMustBePresent === true)
  7598. {
  7599. let keywordIsPresentInDesc = uroUtils.KeywordPresent(desc,keywordPresent,caseInsensitive);
  7600. filterByIncludedKeyword = (filterByIncludedKeyword && (!keywordIsPresentInDesc));
  7601. }
  7602. if(filterKeywordMustBeAbsent === true)
  7603. {
  7604. let keywordIsAbsentInDesc = uroUtils.KeywordPresent(desc,keywordAbsent,caseInsensitive);
  7605. filterByNotIncludedKeyword = (filterByNotIncludedKeyword || keywordIsAbsentInDesc);
  7606. }
  7607.  
  7608. filterByNotIncludedKeyword = (filterByNotIncludedKeyword && filterKeywordMustBeAbsent);
  7609. filterByIncludedKeyword = (filterByIncludedKeyword && filterKeywordMustBePresent);
  7610. if(filterByNotIncludedKeyword || filterByIncludedKeyword)
  7611. {
  7612. mcStyle = 'hidden';
  7613. }
  7614.  
  7615. }
  7616.  
  7617. //lock rank
  7618. if(mcStyle == 'visible')
  7619. {
  7620. if((filterL1 === true) && (rank == 0)) mcStyle = 'hidden';
  7621. if((filterL2 === true) && (rank == 1)) mcStyle = 'hidden';
  7622. if((filterL3 === true) && (rank == 2)) mcStyle = 'hidden';
  7623. if((filterL4 === true) && (rank == 3)) mcStyle = 'hidden';
  7624. if((filterL5 === true) && (rank == 4)) mcStyle = 'hidden';
  7625. if((filterL6 === true) && (rank == 5)) mcStyle = 'hidden';
  7626. }
  7627.  
  7628. // expiry
  7629. if(mcStyle == 'visible')
  7630. {
  7631. if((filterExpiryMustBePresent === true) && (expiry === null)) mcStyle = 'hidden';
  7632. if((filterExpiryMustBeAbsent === true) && (expiry != null)) mcStyle = 'hidden';
  7633. }
  7634.  
  7635. // is following?
  7636. if(mcStyle == 'visible')
  7637. {
  7638. if(mcObj.attributes.isFollowing === true)
  7639. {
  7640. if(filterMyFollowed === true) mcStyle = 'hidden';
  7641. }
  7642. else
  7643. {
  7644. if(filterMyUnfollowed === true) mcStyle = 'hidden';
  7645. }
  7646. }
  7647.  
  7648. if(mcStyle == 'visible')
  7649. {
  7650. if(creatorUser !== null)
  7651. {
  7652. if(mcObj.attributes.createdBy != creatorUser) mcStyle = 'hidden';
  7653. }
  7654. if(filterWRCMC === true)
  7655. {
  7656. if(mcObj.attributes.createdBy == 304740435) mcStyle = 'hidden';
  7657. }
  7658. }
  7659.  
  7660. // custom tags
  7661. if(mcStyle == 'visible')
  7662. {
  7663. if(ukroadworks_ur === true)
  7664. {
  7665. if(filterRoadworks === true) mcStyle = 'hidden';
  7666. }
  7667. else if(construction_ur === true)
  7668. {
  7669. if(filterConstruction === true) mcStyle = 'hidden';
  7670. }
  7671. else if(closure_ur === true)
  7672. {
  7673. if(filterClosure === true) mcStyle = 'hidden';
  7674. }
  7675. else if(event_ur === true)
  7676. {
  7677. if(filterEvent === true) mcStyle = 'hidden';
  7678. }
  7679. else if(note_ur === true)
  7680. {
  7681. if(filterNote === true) mcStyle = 'hidden';
  7682. }
  7683. else if(wslm_ur === true)
  7684. {
  7685. if(filterWSLM === true) mcStyle = 'hidden';
  7686. }
  7687. else if(bog_ur === true)
  7688. {
  7689. if(filterBOG === true) mcStyle = 'hidden';
  7690. }
  7691. else if(difficult_ur === true)
  7692. {
  7693. if(filterDifficult === true) mcStyle = 'hidden';
  7694. }
  7695.  
  7696. if(invertFilters === true)
  7697. {
  7698. if(mcStyle == 'hidden') mcStyle = 'visible';
  7699. else mcStyle = 'hidden';
  7700. }
  7701. }
  7702. }
  7703.  
  7704. let geoID = uroMCLayer.features[mcIdx].geometry.id;
  7705. if(document.getElementById(geoID) !== null)
  7706. {
  7707. document.getElementById(geoID).style.visibility = mcStyle;
  7708. }
  7709. }
  7710. }
  7711. }
  7712. uroDBG.PerfMon(pmFunction, pmTStart);
  7713. }
  7714. function uroFilterURs_onObjectsChanged()
  7715. {
  7716. if(uroFilterPreamble())
  7717. {
  7718. if(uroURDialogIsOpen === true)
  7719. {
  7720. uroFilterURs();
  7721. }
  7722. }
  7723. }
  7724. function uroFilterURs_onObjectsAdded()
  7725. {
  7726. if(uroFilterPreamble())
  7727. {
  7728. }
  7729. }
  7730. function uroFilterURs_onObjectsRemoved()
  7731. {
  7732. if(uroFilterPreamble())
  7733. {
  7734. }
  7735. }
  7736. function uroGetManagedAreas()
  7737. {
  7738. uroManagedAreas = [];
  7739. uroIgnoreAreasUserID = null;
  7740.  
  7741. for(let maObj in W.model.managedAreas.objects)
  7742. {
  7743. if(W.model.managedAreas.objects.hasOwnProperty(maObj))
  7744. {
  7745. uroManagedAreas.push(W.model.managedAreas.objects[maObj]);
  7746. }
  7747. }
  7748. return uroManagedAreas.length;
  7749. }
  7750. function uroCheckGeometryWithinManagedAreas(geo)
  7751. {
  7752. let retval = false;
  7753. let ignoreUserMA = false;
  7754.  
  7755. // If we're ignoring the user's managed area, then we first check to see if
  7756. // the geopoint lies within that - if so then we can skip checking all the
  7757. // other areas in the list...
  7758. if(uroIgnoreAreasUserID !== null)
  7759. {
  7760. for(let uma = 0; uma < uroManagedAreas.length; ++uma)
  7761. {
  7762. if(uroIgnoreAreasUserID == uroManagedAreas[uma].attributes.userID)
  7763. {
  7764. ignoreUserMA = uroContainsPoint(uroManagedAreas[uma].attributes.geoJSONGeometry.coordinates[0], geo);
  7765. break;
  7766. }
  7767. }
  7768. }
  7769.  
  7770. // Point either isn't within the user's area, or we're not ignoring it, so
  7771. // check the rest of the areas in the list
  7772. if(ignoreUserMA == false)
  7773. {
  7774. for(let ma = 0; ma < uroManagedAreas.length; ++ma)
  7775. {
  7776. if(uroIgnoreAreasUserID != uroManagedAreas[ma].attributes.userID)
  7777. {
  7778. retval = uroContainsPoint(uroManagedAreas[ma].attributes.geoJSONGeometry.coordinates[0], geo);
  7779. break;
  7780. }
  7781. }
  7782. }
  7783.  
  7784. return retval;
  7785. }
  7786. function uroGetURDriveGeoms()
  7787. {
  7788. let retval = [];
  7789.  
  7790. for (let urobj in W.model.mapUpdateRequests.objects)
  7791. {
  7792. if(W.model.mapUpdateRequests.objects.hasOwnProperty(urobj))
  7793. {
  7794. let ureq = W.model.mapUpdateRequests.objects[urobj];
  7795. let ureqID = ureq.attributes.id;
  7796.  
  7797. let hasGeo = false;
  7798. let thisRet = [];
  7799. thisRet.push(ureqID);
  7800. thisRet.push(null);
  7801. thisRet.push([]);
  7802.  
  7803. let latMin = 9999;
  7804. let latMax = -9999;
  7805. let lonMin = 9999;
  7806. let lonMax = -9999;
  7807.  
  7808. let urs = W.model.updateRequestSessions.objects[ureqID];
  7809. if((urs !== undefined) && (urs.attributes.driveGeometry !== undefined))
  7810. {
  7811. let cPairs = [];
  7812. for(let i = 0; i < urs.attributes.driveGeometry.coordinates.length; ++i)
  7813. {
  7814. for(let j = 0; j < urs.attributes.driveGeometry.coordinates[i].length; ++j)
  7815. {
  7816. if((i === 0) || (j > 0))
  7817. {
  7818. let coords = urs.attributes.driveGeometry.coordinates[i][j];
  7819. cPairs.push(coords);
  7820.  
  7821. if(coords[0] > lonMax)
  7822. {
  7823. lonMax = coords[0];
  7824. }
  7825. if(coords[0] < lonMin)
  7826. {
  7827. lonMin = coords[0];
  7828. }
  7829. if(coords[1] > latMax)
  7830. {
  7831. latMax = coords[1];
  7832. }
  7833. if(coords[1] < latMin)
  7834. {
  7835. latMin = coords[1];
  7836. }
  7837.  
  7838. hasGeo = true;
  7839. }
  7840. }
  7841. }
  7842. let bbox = [];
  7843. bbox.push(lonMin);
  7844. bbox.push(lonMax);
  7845. bbox.push(latMin);
  7846. bbox.push(latMax);
  7847. thisRet.push(bbox);
  7848. thisRet.push(cPairs);
  7849. }
  7850.  
  7851. if(hasGeo === true)
  7852. {
  7853. retval.push(thisRet);
  7854. }
  7855. }
  7856. }
  7857. return retval;
  7858. }
  7859. function uroCompareDriveGeos(geoA, geoB)
  7860. {
  7861. const matchLength = 5;
  7862. let retval = false;
  7863.  
  7864. if((geoA.length >= matchLength) && (geoB.length >= matchLength))
  7865. {
  7866. for(let i = 0; i < (geoA.length - matchLength); ++i)
  7867. {
  7868. for(let j = 0; j < (geoB.length - matchLength); ++j)
  7869. {
  7870. if((geoA[i][0] == geoB[j][0]) && (geoA[i][1] == geoB[j][1]))
  7871. {
  7872. retval = true;
  7873. for(let k = 1; k < matchLength; ++k)
  7874. {
  7875. if((geoA[i+k][0] != geoB[j+k][0]) || (geoA[i+k][1] != geoB[j+k][1]))
  7876. {
  7877. retval = false;
  7878. break;
  7879. }
  7880. }
  7881. }
  7882. if(retval === true)
  7883. {
  7884. break;
  7885. }
  7886. }
  7887.  
  7888. if(retval === true)
  7889. {
  7890. break;
  7891. }
  7892. }
  7893. }
  7894. return retval;
  7895. }
  7896. function uroCompareDriveBBoxes(bbA, bbB)
  7897. {
  7898. let retval = true;
  7899.  
  7900. if
  7901. (
  7902. (bbA[0] > bbB[1]) ||
  7903. (bbA[1] < bbB[0]) ||
  7904. (bbA[2] > bbB[3]) ||
  7905. (bbA[3] < bbB[2])
  7906. )
  7907. {
  7908. retval = false;
  7909. }
  7910.  
  7911. return retval;
  7912. }
  7913. function uroGetURDupes()
  7914. {
  7915. uroURDupes = [];
  7916.  
  7917. // To determine which URs are duplicates of one another (i.e. have been raised by the same user
  7918. // during the same section of a journey), we first compare the geometries of the drive tracks
  7919. // attached to any URs which have them - as this is based on the users GPS position rather than
  7920. // the WME map data, it makes it vanishingly unlikely that any two users would have identical
  7921. // GPS positions (especially given the level of accuracy to which the track points are stored)
  7922. // even if they were driving exactly the same route at the same speed, in the same lane etc.
  7923. //
  7924. // To accelerate this geometry comparison, we start by performing a simple bounding box overlap
  7925. // check for the two geometries under consideration - if there's no overlap then there can't be
  7926. // any geometry match, so no need to continue onto the more detailed comparision of the GPS
  7927. // tracks themselves...
  7928. let driveGeos = uroGetURDriveGeoms();
  7929. if(driveGeos.length > 1)
  7930. {
  7931. for(let i = 0; i < (driveGeos.length - 1); ++i)
  7932. {
  7933. if(driveGeos[i].length !== 5)
  7934. {
  7935. driveGeos[i][1] = false;
  7936. }
  7937. else
  7938. {
  7939. for(let j = (i + 1); j < driveGeos.length; ++j)
  7940. {
  7941. if(driveGeos[j].length === 5)
  7942. {
  7943. let geoMatch = uroCompareDriveBBoxes(driveGeos[i][3], driveGeos[j][3]);
  7944. if(geoMatch === true)
  7945. {
  7946. geoMatch = uroCompareDriveGeos(driveGeos[i][4], driveGeos[j][4]);
  7947. if(geoMatch === true)
  7948. {
  7949. driveGeos[i][2].push(driveGeos[j][0]);
  7950. driveGeos[j][2].push(driveGeos[i][0]);
  7951. }
  7952. }
  7953. }
  7954. }
  7955. }
  7956. }
  7957.  
  7958. for(let i = 0; i < driveGeos.length; ++i)
  7959. {
  7960. if(driveGeos[i][2].length > 0)
  7961. {
  7962. let res = [];
  7963. res.push(driveGeos[i][0]);
  7964. res.push(driveGeos[i][2]);
  7965. uroURDupes.push(res);
  7966. }
  7967. }
  7968. }
  7969.  
  7970. // Once we've done the initial drive track comparision, uroURDupes will contain a list of
  7971. // all the URs which were matched up based on that. However, as the track comparision is
  7972. // inherently limited by the amount of track data included with each UR, this initial
  7973. // comparison may mean some more widely spaced URs aren't flagged as duplicates simply
  7974. // because there's insufficient overlap between their tracks, even though there may have
  7975. // been further URs dropped inbetween to which they were matched.
  7976. //
  7977. // e.g. if a user drops 4 URs along a section of their journey, spaced such that each of
  7978. // the GPS tracks generates a comparison match with the UR either side of it, but no
  7979. // further than that, we would get:
  7980. //
  7981. // UR A......UR B.......UR C......UR D
  7982. //
  7983. // Matches: A to B, B to A & C, C to B & D, D to C
  7984. //
  7985. // Note how, although we know all of these URs are in fact duplicates, and can infer this
  7986. // from seeing that e.g. A is flagged only as a duplicate of B, however as B is flagged as
  7987. // a duplicate of A & C, and C is flagged as a duplicate of B & D, A MUST therefore be a
  7988. // duplicate of B, C & D...
  7989. //
  7990. // To fix this, we now run a merging pass over each of the entries in uroURDupes - for
  7991. // each entry we see if its ID appears in any of the other entries as a duplicate, and
  7992. // if so we merge the duplicates for both entries.
  7993.  
  7994. for(let i = 0; i < uroURDupes.length; ++i)
  7995. {
  7996. let urID = uroURDupes[i][0];
  7997. for(let j = 0; j < uroURDupes.length; ++j)
  7998. {
  7999. if(i != j)
  8000. {
  8001. if(uroURDupes[j][1].includes(urID) === true)
  8002. {
  8003. // https://stackoverflow.com/questions/1584370/how-to-merge-two-arrays-in-javascript-and-de-duplicate-items
  8004. let mergedDupes = [...new Set([...uroURDupes[i][1], ...uroURDupes[j][1]])];
  8005. uroURDupes[i][1] = mergedDupes;
  8006. uroURDupes[j][1] = mergedDupes;
  8007. }
  8008. }
  8009. }
  8010. }
  8011. }
  8012. function uroFilterURs()
  8013. {
  8014. let pmTStart = performance.now();
  8015. let pmFunction = "uroFilterURs";
  8016.  
  8017. if(uroUserID === -1)
  8018. {
  8019. return;
  8020. }
  8021.  
  8022. if(uroInhibitURFiltering === true)
  8023. {
  8024. return;
  8025. }
  8026.  
  8027.  
  8028. // compatibility fix for URComments - based on code supplied by RickZabel
  8029. let hasActiveURFilters = false;
  8030. if(uroIsFilteringEnabled(false) === true)
  8031. {
  8032. let urTabInputs = uroTabs.CtrlTabs[uroTabs.IDS.URS][uroTabs.FIELDS.TABBODY].getElementsByTagName('input');
  8033. for(let loop = 0; loop < urTabInputs.length; loop++)
  8034. {
  8035. if(urTabInputs[loop].type == 'checkbox')
  8036. {
  8037. let ignoreCB = false;
  8038. ignoreCB = ignoreCB || (urTabInputs[loop].id == '_cbCaseInsensitive');
  8039. ignoreCB = ignoreCB || (urTabInputs[loop].id == '_cbNoFilterForTaggedURs');
  8040. if((urTabInputs[loop].checked) && (ignoreCB === false))
  8041. {
  8042. hasActiveURFilters = true;
  8043. break;
  8044. }
  8045. }
  8046. }
  8047. }
  8048. sessionStorage.UROverview_hasActiveURFilters = hasActiveURFilters;
  8049. if(uroFilterPreamble() === false) return;
  8050. uroRefreshUpdateRequestSessions();
  8051. let selectorResolver = document.getElementById('_selectURResolverID');
  8052. let selectorCommentUser = document.getElementById('_selectURUserID');
  8053. if(uroUtils.GetCBChecked('_cbURResolverIDFilter') === false)
  8054. {
  8055. while(selectorResolver.options.length > 0)
  8056. {
  8057. selectorResolver.options.remove(0);
  8058. }
  8059. }
  8060. if(uroUtils.GetCBChecked('_cbURUserIDFilter') === false)
  8061. {
  8062. while(selectorCommentUser.options.length > 0)
  8063. {
  8064. selectorCommentUser.options.remove(0);
  8065. }
  8066. }
  8067. if(Object.keys(W.model.updateRequestSessions.objects).length === 0)
  8068. {
  8069. // This may be the case if the user has disabled the UR layer, so call
  8070. // AddCommentCounts to clear any existing ones from the map view...
  8071. uroURExtras.AddCommentCounts();
  8072. return;
  8073. }
  8074. let commenterUser = null;
  8075. if(uroUtils.GetCBChecked('_cbURUserIDFilter') === true)
  8076. {
  8077. if(selectorCommentUser.options.length === 0)
  8078. {
  8079. uroUpdateEditorList(W.model.updateRequestSessions.objects, '_selectURUserID', false, false, false, true);
  8080. }
  8081. if(selectorCommentUser.selectedOptions[0] != null)
  8082. {
  8083. commenterUser = parseInt(selectorCommentUser.selectedOptions[0].value);
  8084. }
  8085. }
  8086. let resolverUser = null;
  8087. if(uroUtils.GetCBChecked('_cbURResolverIDFilter') === true)
  8088. {
  8089. if(selectorResolver.options.length === 0)
  8090. {
  8091. uroUpdateEditorList(W.model.mapUpdateRequests.objects, '_selectURResolverID', false, false, true, false);
  8092. }
  8093. if(selectorResolver.selectedOptions[0] != null)
  8094. {
  8095. resolverUser = parseInt(selectorResolver.selectedOptions[0].value);
  8096. }
  8097. }
  8098. uroURExtras.urList = [];
  8099. uroGetURDupes();
  8100.  
  8101. let uFURs_masterEnable = uroIsFilteringEnabled(false);
  8102. let filterOutsideEditableArea = uroUtils.GetCBChecked('_cbURFilterOutsideArea');
  8103. let filterInsideManagedAreas = uroUtils.GetCBChecked('_cbURFilterInsideManagedAreas');
  8104. let filterSolved = uroUtils.GetCBChecked('_cbFilterSolved');
  8105. let filterUnidentified = uroUtils.GetCBChecked('_cbFilterUnidentified');
  8106. let filterClosed = uroUtils.GetCBChecked('_cbFilterClosedUR');
  8107. let filterOpen = uroUtils.GetCBChecked('_cbFilterOpenUR');
  8108. let filterDescMustBePresent = uroUtils.GetCBChecked('_cbURDescriptionMustBePresent');
  8109. let filterDescMustBeAbsent = uroUtils.GetCBChecked('_cbURDescriptionMustBeAbsent');
  8110. let filterKeywordMustBePresent = uroUtils.GetCBChecked('_cbEnableKeywordMustBePresent');
  8111. let filterKeywordMustBeAbsent = uroUtils.GetCBChecked('_cbEnableKeywordMustBeAbsent');
  8112. let filterMinURAge = uroUtils.GetCBChecked('_cbEnableMinAgeFilter');
  8113. let filterMaxURAge = uroUtils.GetCBChecked('_cbEnableMaxAgeFilter');
  8114. let filterMinComments = uroUtils.GetCBChecked('_cbEnableMinCommentsFilter');
  8115. let filterMaxComments = uroUtils.GetCBChecked('_cbEnableMaxCommentsFilter');
  8116. let filterReporterLastCommenter = uroUtils.GetCBChecked('_cbHideIfReporterLastCommenter');
  8117. let filterReporterNotLastCommenter = uroUtils.GetCBChecked('_cbHideIfReporterNotLastCommenter');
  8118. let filterHideAnyComments = uroUtils.GetCBChecked('_cbHideAnyComments');
  8119. let filterHideNotLastCommenter = uroUtils.GetCBChecked('_cbHideIfNotLastCommenter');
  8120. let filterHideMyComments = uroUtils.GetCBChecked('_cbHideMyComments');
  8121. let filterIfLastCommenter = uroUtils.GetCBChecked('_cbHideIfLastCommenter');
  8122. let filterIfNotLastCommenter = uroUtils.GetCBChecked('_cbHideIfNotLastCommenter');
  8123. let filterCommentMinAge = uroUtils.GetCBChecked('_cbEnableCommentAgeFilter2');
  8124. let filterCommentMaxAge = uroUtils.GetCBChecked('_cbEnableCommentAgeFilter');
  8125. let filterUserID = uroUtils.GetCBChecked('_cbURUserIDFilter');
  8126. let filterMyFollowed = uroUtils.GetCBChecked('_cbHideMyFollowed');
  8127. let filterMyUnfollowed = uroUtils.GetCBChecked('_cbHideMyUnfollowed');
  8128.  
  8129. let filterWazeAuto = uroUtils.GetCBChecked('_cbFilterWazeAuto');
  8130. let filterRoadworks = uroUtils.GetCBChecked('_cbFilterRoadworks');
  8131. let filterConstruction = uroUtils.GetCBChecked('_cbFilterConstruction');
  8132. let filterClosure = uroUtils.GetCBChecked('_cbFilterClosure');
  8133. let filterEvent = uroUtils.GetCBChecked('_cbFilterEvent');
  8134. let filterNote = uroUtils.GetCBChecked('_cbFilterNote');
  8135. let filterWSLM = uroUtils.GetCBChecked('_cbFilterWSLM');
  8136. let filterBOG = uroUtils.GetCBChecked('_cbFilterBOG');
  8137. let filterDifficult = uroUtils.GetCBChecked('_cbFilterDifficult');
  8138.  
  8139. let filterIncorrectTurn = uroUtils.GetCBChecked('_cbFilterIncorrectTurn');
  8140. let filterIncorrectAddress = uroUtils.GetCBChecked('_cbFilterIncorrectAddress');
  8141. let filterIncorrectRoute = uroUtils.GetCBChecked('_cbFilterIncorrectRoute');
  8142. let filterMissingRoundabout = uroUtils.GetCBChecked('_cbFilterMissingRoundabout');
  8143. let filterGeneralError = uroUtils.GetCBChecked('_cbFilterGeneralError');
  8144. let filterTurnNotAllowed = uroUtils.GetCBChecked('_cbFilterTurnNotAllowed');
  8145. let filterIncorrectJunction = uroUtils.GetCBChecked('_cbFilterIncorrectJunction');
  8146. let filterMissingBridgeOverpass = uroUtils.GetCBChecked('_cbFilterMissingBridgeOverpass');
  8147. let filterWrongDrivingDirection = uroUtils.GetCBChecked('_cbFilterWrongDrivingDirection');
  8148. let filterMissingExit = uroUtils.GetCBChecked('_cbFilterMissingExit');
  8149. let filterMissingRoad = uroUtils.GetCBChecked('_cbFilterMissingRoad');
  8150. let filterMissingLandmark = uroUtils.GetCBChecked('_cbFilterMissingLandmark');
  8151. let filterNativeSpeedLimit = uroUtils.GetCBChecked('_cbFilterSpeedLimits');
  8152. let filterBlockedRoad = uroUtils.GetCBChecked('_cbFilterBlockedRoad');
  8153. let filterUndefined = uroUtils.GetCBChecked('_cbFilterUndefined');
  8154.  
  8155. let invertURFilters = uroUtils.GetCBChecked('_cbInvertURFilter');
  8156. let invertURStateFilters = uroUtils.GetCBChecked('_cbInvertURStateFilter');
  8157. let noFilterTaggedURs = uroUtils.GetCBChecked('_cbNoFilterForTaggedURs');
  8158. let noFilterURInURL = uroUtils.GetCBChecked('_cbNoFilterForURInURL');
  8159. let showOnlyDupes = uroUtils.GetCBChecked('_cbURFilterDupes');
  8160.  
  8161. let keywordPresent = uroUtils.GetElmValue('_textKeywordPresent');
  8162. let keywordAbsent = uroUtils.GetElmValue('_textKeywordAbsent');
  8163. let caseInsensitive = uroUtils.GetCBChecked('_cbCaseInsensitive');
  8164. let thresholdMinAge = uroUtils.GetElmValue('_inputFilterMinDays');
  8165. let thresholdMaxAge = uroUtils.GetElmValue('_inputFilterMaxDays');
  8166. let thresholdMinComments = uroUtils.GetElmValue('_inputFilterMinComments');
  8167. let thresholdMaxComments = uroUtils.GetElmValue('_inputFilterMaxComments');
  8168. let thresholdMaxCommentAge = uroUtils.GetElmValue('_inputFilterCommentDays');
  8169. let thresholdMinCommentAge = uroUtils.GetElmValue('_inputFilterCommentDays2');
  8170. let ignoreOtherEditorComments = uroUtils.GetCBChecked('_cbIgnoreOtherEditorComments');
  8171. let urcFilteringIsActive = false;
  8172. let urcCB = document.getElementById('URCommentsFilterEnabled');
  8173. if(urcCB !== null)
  8174. {
  8175. if(urcCB.checked)
  8176. {
  8177. urcFilteringIsActive = true;
  8178. }
  8179. }
  8180. urcCB = document.getElementById('URCommentUROOnlyMyUR');
  8181. if(urcCB !== null)
  8182. {
  8183. if(urcCB.checked)
  8184. {
  8185. urcFilteringIsActive = true;
  8186. }
  8187. }
  8188. urcCB = document.getElementById('URCommentUROHideTagged');
  8189. if(urcCB !== null)
  8190. {
  8191. if(urcCB.checked)
  8192. {
  8193. urcFilteringIsActive = true;
  8194. }
  8195. }
  8196.  
  8197. filterInsideManagedAreas = filterInsideManagedAreas && (uroGetManagedAreas() !== 0);
  8198. if(uroUtils.GetCBChecked('_cbURExcludeUserArea') == true)
  8199. {
  8200. uroIgnoreAreasUserID = W.loginManager.user.attributes.id;
  8201. }
  8202.  
  8203. for (let urobj in W.model.mapUpdateRequests.objects)
  8204. {
  8205. if(W.model.mapUpdateRequests.objects.hasOwnProperty(urobj))
  8206. {
  8207. let ureq = W.model.mapUpdateRequests.objects[urobj];
  8208. let ureqID = ureq.attributes.id;
  8209.  
  8210. let urStyle = 'visible';
  8211. let inhibitFiltering = ((ureqID == uroSelectedURID) && (noFilterURInURL));
  8212.  
  8213. let hasMyComments = false;
  8214. let nComments = 0;
  8215. let desc = ureq.attributes.description;
  8216. let customType = uroGetCustomType(ureqID, uroLayers.ID.UR, desc);
  8217. let ageLastComment = null;
  8218. if(W.model.updateRequestSessions.objects[ureqID] != null)
  8219. {
  8220. nComments = W.model.updateRequestSessions.objects[ureqID].attributes.comments.length;
  8221. if(nComments != 0)
  8222. {
  8223. ageLastComment = uroUtils.GetCommentAge(W.model.updateRequestSessions.objects[ureqID].attributes.comments[nComments-1]);
  8224. }
  8225. if((uFURs_masterEnable === false) && (nComments === 0))
  8226. {
  8227. // when master enable is turned off, we want to make sure that all URs, including ones that were previously hidden, are correctly
  8228. // displayed in their native form - i.e. no comment count or custom conversation bubbles. The easiest way to achieve this is to
  8229. // force the AddCommentCounts code to test for the presence of these bubbles on each UR, which we do by setting a non-zero
  8230. // comment count for each UR... For URs which genuinely do have no comments we use -1 to indicate that we're not really setting
  8231. // a comment count, but that we still need to do something that wouldn't be achieved by using 0.
  8232. nComments = -1;
  8233. }
  8234. }
  8235.  
  8236. // check UR against current session ignore list...
  8237. if(uroIgnore.IsOnList(ureqID)) urStyle = 'hidden';
  8238.  
  8239. if((uFURs_masterEnable === true) && (inhibitFiltering === false))
  8240. {
  8241. let wazeauto_ur = false;
  8242. let ukroadworks_ur = false;
  8243. let construction_ur = false;
  8244. let closure_ur = false;
  8245. let event_ur = false;
  8246. let note_ur = false;
  8247. let wslm_ur = false;
  8248. let bog_ur = false;
  8249. let difficult_ur = false;
  8250.  
  8251. let filterByNotIncludedKeyword = false;
  8252. let filterByIncludedKeyword = true;
  8253.  
  8254. if(desc !== null) desc = desc.replace(/<\/?[^>]+(>|$)/g, "");
  8255. else desc = '';
  8256.  
  8257. if(customType === 0) ukroadworks_ur = true;
  8258. else if(customType === 1) construction_ur = true;
  8259. else if(customType === 2) closure_ur = true;
  8260. else if(customType === 3) event_ur = true;
  8261. else if(customType === 4) note_ur = true;
  8262. else if(customType === 5) wslm_ur = true;
  8263. else if(customType === 6) bog_ur = true;
  8264. else if(customType === 7) difficult_ur = true;
  8265.  
  8266. // check UR against editable area...
  8267.  
  8268. if(filterOutsideEditableArea === true)
  8269. {
  8270. if(ureq.canEdit() === false) urStyle = 'hidden';
  8271. }
  8272.  
  8273. if(filterInsideManagedAreas === true)
  8274. {
  8275. if(uroCheckGeometryWithinManagedAreas(ureq.attributes.geoJSONGeometry.coordinates) === true) urStyle = 'hidden';
  8276. }
  8277.  
  8278. if(showOnlyDupes === true)
  8279. {
  8280. let isDupe = false;
  8281. for(let i = 0; i < uroURDupes.length; ++i)
  8282. {
  8283. if(uroURDupes[i][0] === ureqID)
  8284. {
  8285. isDupe = true;
  8286. break;
  8287. }
  8288. }
  8289. if(isDupe === false) urStyle = 'hidden';
  8290. }
  8291.  
  8292. // state-age filtering
  8293. if(urStyle == 'visible')
  8294. {
  8295. // check against closed/not identified filtering if enabled...
  8296. if(filterSolved === true)
  8297. {
  8298. if(ureq.attributes.resolution === 0) urStyle = 'hidden';
  8299. }
  8300. if(filterUnidentified === true)
  8301. {
  8302. if(ureq.attributes.resolution == 1) urStyle = 'hidden';
  8303. }
  8304.  
  8305. if((ureq.attributes.resolvedOn !== null) && (filterClosed === true))
  8306. {
  8307. urStyle = 'hidden';
  8308. }
  8309.  
  8310. if((ureq.attributes.resolvedOn === null) && (filterOpen === true))
  8311. {
  8312. urStyle = 'hidden';
  8313. }
  8314.  
  8315. if(urStyle == 'visible')
  8316. {
  8317. // check UR against keyword filtering if enabled...
  8318. if(filterDescMustBePresent === true)
  8319. {
  8320. if(desc === '') urStyle = 'hidden';
  8321. }
  8322. if(filterDescMustBeAbsent === true)
  8323. {
  8324. if(desc !== '') urStyle = 'hidden';
  8325. }
  8326.  
  8327. if(filterKeywordMustBePresent === true)
  8328. {
  8329. let keywordIsPresentInDesc = uroUtils.KeywordPresent(desc,keywordPresent,caseInsensitive);
  8330. filterByIncludedKeyword = (filterByIncludedKeyword && (!keywordIsPresentInDesc));
  8331. }
  8332. if(filterKeywordMustBeAbsent === true)
  8333. {
  8334. let keywordIsAbsentInDesc = uroUtils.KeywordPresent(desc,keywordAbsent,caseInsensitive);
  8335. filterByNotIncludedKeyword = (filterByNotIncludedKeyword || keywordIsAbsentInDesc);
  8336. }
  8337. }
  8338.  
  8339. if(urStyle == 'visible')
  8340. {
  8341. // do age-based filtering if enabled
  8342. if(filterMinURAge === true)
  8343. {
  8344. if(uroUtils.GetURAge(ureq,0,false) < thresholdMinAge) urStyle = 'hidden';
  8345. }
  8346. if(filterMaxURAge === true)
  8347. {
  8348. if(uroUtils.GetURAge(ureq,0,false) > thresholdMaxAge) urStyle = 'hidden';
  8349. }
  8350. }
  8351.  
  8352. if(urStyle == 'visible')
  8353. {
  8354. if(resolverUser !== null)
  8355. {
  8356. if(ureq.attributes.resolvedBy != resolverUser) urStyle = 'hidden';
  8357. }
  8358. }
  8359.  
  8360. if(urStyle == 'visible')
  8361. {
  8362. // do comments/following filtering
  8363. if(W.model.updateRequestSessions.objects[ureqID] != null)
  8364. {
  8365. nComments = W.model.updateRequestSessions.objects[ureqID].attributes.comments.length;
  8366. let commentDaysOld = -1;
  8367.  
  8368.  
  8369. if(filterMinComments === true)
  8370. {
  8371. if(nComments < thresholdMinComments) urStyle = 'hidden';
  8372. }
  8373. if(filterMaxComments === true)
  8374. {
  8375. if(nComments > thresholdMaxComments) urStyle = 'hidden';
  8376. }
  8377.  
  8378.  
  8379. if(nComments > 0)
  8380. {
  8381. let reporterIsLastCommenter = false;
  8382. if(W.model.updateRequestSessions.objects[ureqID].attributes.comments[nComments-1].userID == -1) reporterIsLastCommenter = true;
  8383.  
  8384. if(filterReporterLastCommenter === true)
  8385. {
  8386. if(reporterIsLastCommenter === true) urStyle = 'hidden';
  8387. }
  8388. else if(filterReporterNotLastCommenter === true)
  8389. {
  8390. if(reporterIsLastCommenter === false) urStyle = 'hidden';
  8391. }
  8392.  
  8393. hasMyComments = uroURHasMyComments(ureqID);
  8394. if(hasMyComments === false)
  8395. {
  8396. if(filterHideAnyComments === true) urStyle = 'hidden';
  8397. if(filterHideNotLastCommenter === true) urStyle = 'hidden';
  8398. }
  8399. else
  8400. {
  8401. if(filterHideMyComments === true) urStyle = 'hidden';
  8402.  
  8403. let userIsLastCommenter = false;
  8404. if(W.model.updateRequestSessions.objects[ureqID].attributes.comments[nComments-1].userID == uroUserID) userIsLastCommenter = true;
  8405.  
  8406. if(filterIfLastCommenter === true)
  8407. {
  8408. if(userIsLastCommenter === true) urStyle = 'hidden';
  8409. }
  8410. else if(filterIfNotLastCommenter === true)
  8411. {
  8412. if(userIsLastCommenter === false) urStyle = 'hidden';
  8413. }
  8414. }
  8415.  
  8416. let cidx;
  8417. if(ignoreOtherEditorComments === false)
  8418. {
  8419. commentDaysOld = ageLastComment;
  8420. }
  8421. else
  8422. {
  8423. for(cidx=0; cidx<nComments; cidx++)
  8424. {
  8425. let cObj = W.model.updateRequestSessions.objects[ureqID].attributes.comments[cidx];
  8426. if((cObj.userID == uroUserID) || (cObj.userID == -1))
  8427. {
  8428. commentDaysOld = uroUtils.GetCommentAge(cObj);
  8429. }
  8430. }
  8431. }
  8432. if((filterCommentMinAge === true) && (commentDaysOld != -1))
  8433. {
  8434. if(thresholdMinCommentAge > commentDaysOld) urStyle = 'hidden';
  8435. }
  8436. if((filterCommentMaxAge === true) && (commentDaysOld != -1))
  8437. {
  8438. if(thresholdMaxCommentAge < commentDaysOld) urStyle = 'hidden';
  8439. }
  8440.  
  8441. if((commenterUser !== null) && (urStyle != 'hidden'))
  8442. {
  8443. urStyle = 'hidden';
  8444. for(cidx=0; cidx<nComments; cidx++)
  8445. {
  8446. if(W.model.updateRequestSessions.objects[ureqID].attributes.comments[cidx].userID == commenterUser)
  8447. {
  8448. urStyle = 'visible';
  8449. break;
  8450. }
  8451. }
  8452. }
  8453.  
  8454. let commentText = '';
  8455. for(cidx=0; cidx<nComments; cidx++)
  8456. {
  8457. commentText += W.model.updateRequestSessions.objects[ureqID].attributes.comments[cidx].text;
  8458. }
  8459.  
  8460. if(filterKeywordMustBePresent === true)
  8461. {
  8462. let keywordIsPresentInComments = uroUtils.KeywordPresent(commentText,keywordPresent,caseInsensitive);
  8463. filterByIncludedKeyword = (filterByIncludedKeyword && (!keywordIsPresentInComments));
  8464. }
  8465. if(filterKeywordMustBeAbsent === true)
  8466. {
  8467. let keywordIsAbsentInComments = uroUtils.KeywordPresent(commentText,keywordAbsent,caseInsensitive);
  8468. filterByNotIncludedKeyword = (filterByNotIncludedKeyword || keywordIsAbsentInComments);
  8469. }
  8470. }
  8471. else
  8472. {
  8473. if(filterUserID === true)
  8474. {
  8475. urStyle = 'hidden';
  8476. }
  8477. }
  8478.  
  8479. filterByNotIncludedKeyword = (filterByNotIncludedKeyword && filterKeywordMustBeAbsent);
  8480. filterByIncludedKeyword = (filterByIncludedKeyword && filterKeywordMustBePresent);
  8481. if(filterByNotIncludedKeyword || filterByIncludedKeyword)
  8482. {
  8483. urStyle = 'hidden';
  8484. }
  8485.  
  8486. if(W.model.updateRequestSessions.objects[ureqID].attributes.isFollowing === true)
  8487. {
  8488. if(filterMyFollowed === true) urStyle = 'hidden';
  8489. }
  8490. else
  8491. {
  8492. if(filterMyUnfollowed === true) urStyle = 'hidden';
  8493. }
  8494. }
  8495. }
  8496.  
  8497. if(invertURStateFilters === true)
  8498. {
  8499. if(urStyle == 'hidden') urStyle = 'visible';
  8500. else urStyle = 'hidden';
  8501. }
  8502. }
  8503.  
  8504. // type filtering
  8505. if(urStyle == 'visible')
  8506. {
  8507. // Test for Waze automatic URs before any others - these always (?) get inserted as General Error URs,
  8508. // so we can't filter them by type...
  8509. if(desc.indexOf('Waze Automatic:') != -1)
  8510. {
  8511. wazeauto_ur = true;
  8512. }
  8513.  
  8514. if(wazeauto_ur === true)
  8515. {
  8516. if(filterWazeAuto === true) urStyle = 'hidden';
  8517. }
  8518.  
  8519. else if(ukroadworks_ur === true)
  8520. {
  8521. if(filterRoadworks === true) urStyle = 'hidden';
  8522. }
  8523. else if(construction_ur === true)
  8524. {
  8525. if(filterConstruction === true) urStyle = 'hidden';
  8526. }
  8527. else if(closure_ur === true)
  8528. {
  8529. if(filterClosure === true) urStyle = 'hidden';
  8530. }
  8531. else if(event_ur === true)
  8532. {
  8533. if(filterEvent === true) urStyle = 'hidden';
  8534. }
  8535. else if(note_ur === true)
  8536. {
  8537. if(filterNote === true) urStyle = 'hidden';
  8538. }
  8539. else if(wslm_ur === true)
  8540. {
  8541. if(filterWSLM === true) urStyle = 'hidden';
  8542. }
  8543. else if(bog_ur === true)
  8544. {
  8545. if(filterBOG === true) urStyle = 'hidden';
  8546. }
  8547. else if(difficult_ur === true)
  8548. {
  8549. if(filterDifficult === true) urStyle = 'hidden';
  8550. }
  8551.  
  8552. else if(ureq.attributes.type == 6)
  8553. {
  8554. if(filterIncorrectTurn === true) urStyle = 'hidden';
  8555. }
  8556. else if(ureq.attributes.type == 7)
  8557. {
  8558. if (filterIncorrectAddress === true) urStyle = 'hidden';
  8559. }
  8560. else if(ureq.attributes.type == 8)
  8561. {
  8562. if(filterIncorrectRoute === true) urStyle = 'hidden';
  8563. }
  8564. else if(ureq.attributes.type == 9)
  8565. {
  8566. if(filterMissingRoundabout === true) urStyle = 'hidden';
  8567. }
  8568. else if(ureq.attributes.type == 10)
  8569. {
  8570. if(filterGeneralError === true) urStyle = 'hidden';
  8571. }
  8572. else if(ureq.attributes.type == 11)
  8573. {
  8574. if(filterTurnNotAllowed === true) urStyle = 'hidden';
  8575. }
  8576. else if(ureq.attributes.type == 12)
  8577. {
  8578. if(filterIncorrectJunction === true) urStyle = 'hidden';
  8579. }
  8580. else if(ureq.attributes.type == 13)
  8581. {
  8582. if(filterMissingBridgeOverpass === true) urStyle = 'hidden';
  8583. }
  8584. else if(ureq.attributes.type == 14)
  8585. {
  8586. if(filterWrongDrivingDirection === true) urStyle = 'hidden';
  8587. }
  8588. else if(ureq.attributes.type == 15)
  8589. {
  8590. if(filterMissingExit === true) urStyle = 'hidden';
  8591. }
  8592. else if(ureq.attributes.type == 16)
  8593. {
  8594. if(filterMissingRoad === true) urStyle = 'hidden';
  8595. }
  8596. else if(ureq.attributes.type == 18)
  8597. {
  8598. if(filterMissingLandmark === true) urStyle = 'hidden';
  8599. }
  8600. else if(ureq.attributes.type == 19)
  8601. {
  8602. if(filterBlockedRoad === true) urStyle = 'hidden';
  8603. }
  8604. else if(ureq.attributes.type == 23)
  8605. {
  8606. if(filterNativeSpeedLimit === true) urStyle = 'hidden';
  8607. }
  8608. else if(filterUndefined === true) urStyle = 'hidden';
  8609.  
  8610. if(invertURFilters === true)
  8611. {
  8612. if(urStyle == 'hidden') urStyle = 'visible';
  8613. else urStyle = 'hidden';
  8614. }
  8615. }
  8616.  
  8617. // stage-age filtering override for tagged URs
  8618. if(noFilterTaggedURs === true)
  8619. {
  8620. if(ukroadworks_ur === true)
  8621. {
  8622. if(filterRoadworks === false) urStyle = 'visible';
  8623. }
  8624. else if(construction_ur === true)
  8625. {
  8626. if(filterConstruction === false) urStyle = 'visible';
  8627. }
  8628. else if(closure_ur === true)
  8629. {
  8630. if(filterClosure === false) urStyle = 'visible';
  8631. }
  8632. else if(event_ur === true)
  8633. {
  8634. if(filterEvent === false) urStyle = 'visible';
  8635. }
  8636. else if(note_ur === true)
  8637. {
  8638. if(filterNote === false) urStyle = 'visible';
  8639. }
  8640. else if(wslm_ur === true)
  8641. {
  8642. if(filterWSLM === false) urStyle = 'visible';
  8643. }
  8644. }
  8645. }
  8646. // only touch marker visibility if we've got active filter settings, or if URComments is not
  8647. // doing any filtering of its own
  8648. if((hasActiveURFilters === true) || (urcFilteringIsActive === false) || (uFURs_masterEnable === false))
  8649. {
  8650. let urMarker = uroGetMarker(uroLayers.ID.UR,urobj);
  8651. if(urMarker !== null)
  8652. {
  8653. urMarker.style.visibility = urStyle;
  8654. }
  8655. }
  8656. if(urStyle != 'hidden')
  8657. {
  8658. uroURExtras.AddToList(ureqID, customType, hasMyComments, nComments, ageLastComment);
  8659. }
  8660. }
  8661. }
  8662. uroURExtras.AddCommentCounts();
  8663. uroDBG.PerfMon(pmFunction, pmTStart);
  8664. }
  8665. function uroGetProblemTypes()
  8666. {
  8667. uroKnownProblemTypeIDs = [];
  8668. uroKnownProblemTypeNames = [];
  8669. let tProblemList = I18n.lookup("problems.types");
  8670. for(let tObj in tProblemList)
  8671. {
  8672. if(tObj !== undefined)
  8673. {
  8674. uroKnownProblemTypeIDs.push(parseInt(tObj));
  8675. uroKnownProblemTypeNames.push(tProblemList[tObj].title);
  8676. }
  8677. }
  8678. }
  8679. function uroFilterProblems()
  8680. {
  8681. let pmTStart = performance.now();
  8682. let pmFunction = "uroFilterProblems";
  8683.  
  8684. if(uroFilterPreamble() === false) return;
  8685. let selector;
  8686.  
  8687. if((uroUtils.GetCBChecked('_cbMPNotClosedUserIDFilter') === false) && (uroUtils.GetCBChecked('_cbMPClosedUserIDFilter') === false))
  8688. {
  8689. selector = document.getElementById('_selectMPUserID');
  8690. while(selector.options.length > 0)
  8691. {
  8692. selector.options.remove(0);
  8693. }
  8694. }
  8695.  
  8696. let solverUser = null;
  8697. if((uroUtils.GetCBChecked('_cbMPNotClosedUserIDFilter') === true) || (uroUtils.GetCBChecked('_cbMPClosedUserIDFilter') === true))
  8698. {
  8699. selector = document.getElementById('_selectMPUserID');
  8700. if(selector.options.length === 0)
  8701. {
  8702. uroUpdateEditorList(W.model.mapProblems.objects, '_selectMPUserID', false, false, true, false);
  8703. }
  8704. if(selector.selectedOptions[0] != null)
  8705. {
  8706. solverUser = parseInt(selector.selectedOptions[0].value);
  8707. }
  8708. }
  8709.  
  8710. let uFP_masterEnable = uroIsFilteringEnabled(false);
  8711. let filter_OutsideEditableArea = uroUtils.GetCBChecked('_cbMPFilterOutsideArea');
  8712. let filter_Solved = uroUtils.GetCBChecked('_cbMPFilterSolved');
  8713. let filter_Unidentified = uroUtils.GetCBChecked('_cbMPFilterUnidentified');
  8714. let filter_Closed = uroUtils.GetCBChecked('_cbMPFilterClosed');
  8715. let filter_NotClosedUserID = uroUtils.GetCBChecked('_cbMPNotClosedUserIDFilter');
  8716. let filter_ClosedUserID = uroUtils.GetCBChecked('_cbMPClosedUserIDFilter');
  8717. let filter_Reopened = uroUtils.GetCBChecked('_cbMPFilterReopenedProblem');
  8718.  
  8719. let filter_LowSeverity = uroUtils.GetCBChecked('_cbMPFilterLowSeverity');
  8720. let filter_MediumSeverity = uroUtils.GetCBChecked('_cbMPFilterMediumSeverity');
  8721. let filter_HighSeverity = uroUtils.GetCBChecked('_cbMPFilterHighSeverity');
  8722.  
  8723. let filterTypes = [];
  8724. let i;
  8725. for(i=0; i<uroKnownProblemTypeIDs.length; i++)
  8726. {
  8727. if(uroUtils.GetCBChecked('_cbMPFilter_T'+uroKnownProblemTypeIDs[i])) filterTypes.push(uroKnownProblemTypeIDs[i]);
  8728. }
  8729. let filter_TypeUnknown = uroUtils.GetCBChecked('_cbMPFilterUnknownProblem');
  8730.  
  8731. let filter_TaggedElgin = uroUtils.GetCBChecked('_cbFilterElgin');
  8732. let filter_TaggedTrafficCast = uroUtils.GetCBChecked('_cbFilterTrafficCast');
  8733. let filter_TaggedTrafficMaster = uroUtils.GetCBChecked('_cbFilterTrafficMaster');
  8734. let filter_TaggedCaltrans = uroUtils.GetCBChecked('_cbFilterCaltrans');
  8735. let filter_TaggedTFL = uroUtils.GetCBChecked('_cbFilterTFL');
  8736.  
  8737. let filter_Invert = uroUtils.GetCBChecked('_cbInvertMPFilter');
  8738.  
  8739. let filter_StartDateEnabled = uroUtils.GetCBChecked('_cbMPFilterStartDate');
  8740. let filter_EndDateEnabled = uroUtils.GetCBChecked('_cbMPFilterEndDate');
  8741. let filter_EndDatePassed = uroUtils.GetCBChecked('_cbMPFilterEndDatePassed');
  8742.  
  8743. let tsD = uroUtils.GetElmValue('_inputMPFilterStartDay');
  8744. let tsM = uroUtils.GetElmValue('_inputMPFilterStartMonth');
  8745. let tsY = uroUtils.GetElmValue('_inputMPFilterStartYear');
  8746. let startDate = uroUtils.GetTS(tsD, tsM, tsY, 0, 0);
  8747.  
  8748. tsD = uroUtils.GetElmValue('_inputMPFilterEndDay');
  8749. tsM = uroUtils.GetElmValue('_inputMPFilterEndMonth');
  8750. tsY = uroUtils.GetElmValue('_inputMPFilterEndYear');
  8751. let endDate = uroUtils.GetTS(tsD, tsM, tsY, 0, 0);
  8752. let nowTime = (new Date()).getTime();
  8753.  
  8754. for (let urobj in W.model.mapProblems.objects)
  8755. {
  8756. if(W.model.mapProblems.objects.hasOwnProperty(urobj))
  8757. {
  8758. let problem = W.model.mapProblems.objects[urobj];
  8759.  
  8760. if(problem.attributes.origJSONGeo === undefined)
  8761. {
  8762. // Store a copy of the original marker position if we haven't already done so
  8763. problem.attributes.origJSONGeo = uroUtils.CloneObject(problem.attributes.geoJSONGeometry);
  8764. }
  8765. else
  8766. {
  8767. // Restore the original position if we do have a copy of it, to undo any adjustments
  8768. // that may have been made in the last filtering pass
  8769. problem.attributes.geoJSONGeometry = uroUtils.CloneObject(problem.attributes.origJSONGeo);
  8770. }
  8771.  
  8772.  
  8773. let ureqID = problem.attributes.id;
  8774.  
  8775. let problemStyle = 'visible';
  8776. // check problem against current session ignore list...
  8777. if(uroIgnore.IsOnList(ureqID)) problemStyle = 'hidden';
  8778.  
  8779. if(uFP_masterEnable === true)
  8780. {
  8781. if(filter_OutsideEditableArea === true)
  8782. {
  8783. if(problem.canEdit() === false)
  8784. {
  8785. problemStyle = 'hidden';
  8786. }
  8787. }
  8788.  
  8789. if(filter_EndDatePassed == true)
  8790. {
  8791. if(problem.attributes.endTime > nowTime)
  8792. {
  8793. problemStyle = 'hidden';
  8794. }
  8795. }
  8796. if(filter_StartDateEnabled == true)
  8797. {
  8798. let tStart = new Date(problem.attributes.startTime);
  8799. tStart.setHours(0);
  8800. tStart.setMinutes(0);
  8801. tStart.setSeconds(0);
  8802. tStart = tStart.getTime();
  8803. if(tStart != startDate)
  8804. {
  8805. problemStyle = 'hidden';
  8806. }
  8807. }
  8808. if(filter_EndDateEnabled == true)
  8809. {
  8810. let tEnd = new Date(problem.attributes.endTime);
  8811. tEnd.setHours(0);
  8812. tEnd.setMinutes(0);
  8813. tEnd.setSeconds(0);
  8814. tEnd = tEnd.getTime();
  8815. if(tEnd != endDate)
  8816. {
  8817. problemStyle = 'hidden';
  8818. }
  8819. }
  8820.  
  8821. // check against closed/not identified filtering if enabled...
  8822. let geoID = problem.getOLGeometry().id;
  8823. if(geoID !== null)
  8824. {
  8825. if(document.getElementById(geoID) !== null)
  8826. {
  8827. let problem_marker_img = document.getElementById(geoID).href.baseVal;
  8828. if(filter_Solved === true)
  8829. {
  8830. if(problem_marker_img.indexOf('_solved') != -1) problemStyle = 'hidden';
  8831. }
  8832. if(filter_Unidentified === true)
  8833. {
  8834. if(problem_marker_img.indexOf('_rejected') != -1) problemStyle = 'hidden';
  8835. }
  8836. }
  8837. }
  8838.  
  8839. if(filter_Closed === true)
  8840. {
  8841. if(problem.attributes.open === false)
  8842. {
  8843. problemStyle = 'hidden';
  8844. }
  8845. }
  8846.  
  8847. if(problemStyle == 'visible')
  8848. {
  8849. if(solverUser !== null)
  8850. {
  8851. if((filter_NotClosedUserID === true) && (problem.attributes.resolvedBy == solverUser)) problemStyle = 'hidden';
  8852. if((filter_ClosedUserID === true) && (problem.attributes.resolvedBy != solverUser)) problemStyle = 'hidden';
  8853. }
  8854. }
  8855.  
  8856. if(problemStyle == 'visible')
  8857. {
  8858. let problemType = problem.attributes.subType;
  8859. let desc = '';
  8860. if(problem.attributes.description != null)
  8861. {
  8862. desc = problem.attributes.description;
  8863. }
  8864. let customType = uroGetCustomType(ureqID, uroLayers.ID.MP, desc);
  8865.  
  8866. if(customType === 100)
  8867. {
  8868. if(filter_TaggedElgin === true) problemStyle = 'hidden';
  8869. }
  8870. else if(customType === 101)
  8871. {
  8872. if(filter_TaggedTrafficCast === true) problemStyle = 'hidden';
  8873. }
  8874. else if(customType === 102)
  8875. {
  8876. if(filter_TaggedTrafficMaster === true) problemStyle = 'hidden';
  8877. }
  8878. else if(customType === 103)
  8879. {
  8880. if(filter_TaggedCaltrans === true) problemStyle = 'hidden';
  8881. }
  8882. else if(customType === 104)
  8883. {
  8884. if(filter_TaggedTFL === true) problemStyle = 'hidden';
  8885. }
  8886. else if(uroKnownProblemTypeIDs.indexOf(problemType) !== -1)
  8887. {
  8888. if(filterTypes.indexOf(problemType) !== -1)
  8889. {
  8890. problemStyle = 'hidden';
  8891. }
  8892. }
  8893. else if(filter_TypeUnknown === true) problemStyle = 'hidden';
  8894.  
  8895. if(filter_Reopened === true)
  8896. {
  8897. if((problem.attributes.open === true) && (problem.attributes.resolvedOn !== null))
  8898. {
  8899. problemStyle = 'hidden';
  8900. }
  8901. }
  8902.  
  8903.  
  8904. if(filter_Invert === true)
  8905. {
  8906. if(problemStyle == 'hidden') problemStyle = 'visible';
  8907. else problemStyle = 'hidden';
  8908. }
  8909.  
  8910.  
  8911. if(problem.attributes.weight <= 3)
  8912. {
  8913. if(filter_LowSeverity === true) problemStyle = 'hidden';
  8914. }
  8915. else if(problem.attributes.weight <= 7)
  8916. {
  8917. if(filter_MediumSeverity === true) problemStyle = 'hidden';
  8918. }
  8919. else if(filter_HighSeverity === true) problemStyle = 'hidden';
  8920. }
  8921. }
  8922.  
  8923. let marker = uroGetMarker(uroLayers.ID.MP, urobj);
  8924. if(marker !== null)
  8925. {
  8926. marker.style.visibility = problemStyle;
  8927. if(problemStyle === 'hidden')
  8928. {
  8929. // To ensure WME displays the details for the topmost marker that's
  8930. // left visible in a stack, alter the coords for any hidden markers
  8931. // to place them in a location that isn't likely to be clicked on...
  8932. problem.attributes.geoJSONGeometry.coordinates[0] = 0;
  8933. problem.attributes.geoJSONGeometry.coordinates[1] = 90;
  8934. }
  8935. }
  8936. }
  8937. }
  8938.  
  8939. uroDBG.PerfMon(pmFunction, pmTStart);
  8940. }
  8941. function uroFilterPreamble()
  8942. {
  8943. let mapviewport = document.getElementsByClassName("olMapViewport")[0];
  8944. if(mapviewport === null)
  8945. {
  8946. if(uroNullMapViewport === false)
  8947. {
  8948. uroDBG.AddLog('caught null mapviewport');
  8949. uroNullMapViewport = true;
  8950. }
  8951. return false;
  8952. }
  8953.  
  8954. let uiElms = uroTabs.CtrlTabs[uroTabs.IDS.MISC][uroTabs.FIELDS.TABBODY];
  8955. if(uiElms == null)
  8956. {
  8957. uroDBG.AddLog('caught missing UI');
  8958. return false;
  8959. }
  8960. if(uiElms.innerHTML.length === 0)
  8961. {
  8962. uroDBG.AddLog('caught empty UI');
  8963. return false;
  8964. }
  8965.  
  8966. if(uroSettingsApplied === false)
  8967. {
  8968. return false;
  8969. }
  8970.  
  8971. uroNullMapViewport = false;
  8972.  
  8973. return true;
  8974. }
  8975. function uroFilterItems_MasterEnableClick()
  8976. {
  8977. if(uroUtils.GetCBChecked('_cbMasterEnable') === false)
  8978. {
  8979. uroPopup.Hide();
  8980. }
  8981. uroFilterItems();
  8982. }
  8983. function uroFilterItems()
  8984. {
  8985. uroFilterProblems();
  8986. uroFilterPlaces();
  8987. uroFilterCameras();
  8988. uroFilterURs();
  8989. uroFilterRTCs();
  8990. uroFilterRAs();
  8991. uroFilterMapComments();
  8992. }
  8993. function uroFilterItemsOnMove()
  8994. {
  8995. W.map.events.unregister('mousemove',null,uroFilterItemsOnMove);
  8996. uroFilterItems();
  8997. }
  8998. function uroDeleteObject()
  8999. {
  9000. uroDBG.AddLog('delete camera ID '+uroShownFID);
  9001. if(W.model.cameras.objects[uroShownFID] === null)
  9002. {
  9003. uroDBG.AddLog('camera object not found...');
  9004. return false;
  9005. }
  9006. uroOWL.RemoveCamFromWatchList();
  9007. let actionObj = require('Waze/Action/DeleteObject');
  9008. let deleteAction = new actionObj(W.model.cameras.objects[uroShownFID], null);
  9009. W.model.actionManager.add(deleteAction);
  9010. uroPopup.MouseOut();
  9011. uroPopup.Hide();
  9012. return false;
  9013. }
  9014. function uroCheckCommentsForTag(idSrc)
  9015. {
  9016. let ursObj = W.model.updateRequestSessions.objects[idSrc];
  9017. if(typeof(ursObj) == 'undefined') return -1;
  9018. if(ursObj.attributes.comments.length === 0) return -1;
  9019.  
  9020. for(let idx=ursObj.attributes.comments.length-1; idx>=0; idx--)
  9021. {
  9022. for(let tag=0; tag<uroCustomURTags.length; tag++)
  9023. {
  9024. let keyword = uroCustomURTags[tag];
  9025. if(ursObj.attributes.comments[idx].text.indexOf(keyword) != -1)
  9026. {
  9027. return tag;
  9028. }
  9029. }
  9030. }
  9031. return -1;
  9032. }
  9033. function uroGetCustomType(idSrc, markerType, desc)
  9034. {
  9035. let provider = '';
  9036. if(desc === null) desc = '';
  9037. if(markerType == uroLayers.ID.UR)
  9038. {
  9039. let ureq = W.model.mapUpdateRequests.objects[idSrc];
  9040. // early test for native speed limit URs
  9041. if(ureq.attributes.type == 23) return 98;
  9042. }
  9043. else if(markerType == uroLayers.ID.MP)
  9044. {
  9045. let mp = W.model.mapProblems.objects[idSrc];
  9046. if(mp.attributes.provider != null)
  9047. {
  9048. provider = mp.attributes.provider;
  9049. }
  9050. }
  9051.  
  9052. if(desc !== '')
  9053. {
  9054. if((markerType == uroLayers.ID.UR) || (markerType == 'mc'))
  9055. {
  9056. for(let tag=0; tag<uroCustomURTags.length; tag++)
  9057. {
  9058. let keyword = uroCustomURTags[tag];
  9059. if(desc.indexOf(keyword) != -1)
  9060. {
  9061. return tag;
  9062. }
  9063. }
  9064. }
  9065.  
  9066. if(markerType == uroLayers.ID.MP)
  9067. {
  9068. if(desc.indexOf('[Elgin]') != -1) return 100;
  9069. if(desc.indexOf('[ELGIN]') != -1) return 100;
  9070. if(desc.indexOf('[elginroadworks]') != -1) return 100;
  9071. if(desc.indexOf('[one.network]') != -1) return 100;
  9072. if(desc.indexOf('[TrafficCast]') != -1) return 101;
  9073. if(desc.indexOf('[TM]') != -1) return 102;
  9074. if(desc.indexOf('[Caltrans]') != -1) return 103;
  9075. if(desc.indexOf('[TfL Open Data]') != -1) return 104;
  9076. if(provider.indexOf('London TFL Closures') != -1) return 104;
  9077. }
  9078. }
  9079.  
  9080. if(markerType == uroLayers.ID.UR)
  9081. {
  9082. return uroCheckCommentsForTag(idSrc);
  9083. }
  9084.  
  9085. return -1;
  9086. }
  9087. function uroGetRestrictionLanes(disposition)
  9088. {
  9089. let retval = '';
  9090. if(disposition == 1) retval += 'All lanes';
  9091. else if(disposition == 2) retval += 'Left lane';
  9092. else if(disposition == 3) retval += 'Middle lane';
  9093. else if(disposition == 4) retval += 'Right lane';
  9094. else retval += ' - ';
  9095. return retval;
  9096. }
  9097. function uroGetRestrictionLaneType(laneType)
  9098. {
  9099. let retval = '';
  9100. if(laneType === null) retval += ' - ';
  9101. else
  9102. {
  9103. if(laneType == 1) retval += 'HOV';
  9104. else if(laneType == 2) retval += 'HOT';
  9105. else if(laneType == 3) retval += 'Express';
  9106. else if(laneType == 4) retval += 'Bus lane';
  9107. else if(laneType == 5) retval += 'Fast lane';
  9108. else retval += ' - ';
  9109. }
  9110. return retval;
  9111. }
  9112. let uroVehicleTypes =
  9113. [
  9114. [1280, 'fa-car'],
  9115. [1024, 'fa-motorcycle'],
  9116. [272, 'fa-taxi'],
  9117. [1808, 'fa-bolt']
  9118. ];
  9119. function uroGetRestrictionVehicleTypes(restObj, allowInit, profileKey)
  9120. {
  9121. let i;
  9122. let j;
  9123. let k;
  9124. let tVT;
  9125. let retval = [];
  9126. for(i = 0; i < uroVehicleTypes.length; ++i)
  9127. {
  9128. retval.push(allowInit);
  9129. }
  9130. let tRest = restObj._driveProfiles.get(profileKey);
  9131. if(tRest !== undefined)
  9132. {
  9133. for(i = 0; i < tRest._driveProfiles.length; ++i)
  9134. {
  9135. tVT = tRest._driveProfiles[i].getVehicleTypes();
  9136. {
  9137. if(tVT.length > 0)
  9138. {
  9139. for(j = 0; j < tVT.length; ++j)
  9140. {
  9141. for(k = 0; k < uroVehicleTypes.length; ++k)
  9142. {
  9143. if(tVT[j] == uroVehicleTypes[k][0])
  9144. {
  9145. retval[k] = !allowInit;
  9146. }
  9147. }
  9148. }
  9149. }
  9150. }
  9151. }
  9152. }
  9153. return retval;
  9154. }
  9155. function uroFormatRestriction(restObj)
  9156. {
  9157. let retval = '';
  9158.  
  9159. if(restObj._defaultType == "DIFFICULT")
  9160. {
  9161. retval = '<tr><td colspan=13>Difficult Turn';
  9162. }
  9163. else
  9164. {
  9165. let roDays = null;
  9166. let roFromDate = null;
  9167. let roToDate = null;
  9168. let roFromTime = null;
  9169. let roToTime = null;
  9170. let roRepeats = false;
  9171. let roAllDay = false;
  9172. if(restObj._days !== undefined)
  9173. {
  9174. roDays = restObj._days;
  9175. roFromDate = restObj._fromDate;
  9176. roToDate = restObj._toDate;
  9177. roFromTime = restObj._fromTime;
  9178. roToTime = restObj._toTime;
  9179. }
  9180. else if(restObj._timeFrames.length > 0)
  9181. {
  9182. if(restObj._timeFrames[0]._weekdays !== undefined)
  9183. {
  9184. roDays = restObj._timeFrames[0]._weekdays;
  9185. roFromDate = restObj._timeFrames[0]._startDate;
  9186. roToDate = restObj._timeFrames[0]._endDate;
  9187. roFromTime = restObj._timeFrames[0]._fromTime;
  9188. roToTime = restObj._timeFrames[0]._toTime;
  9189. roRepeats = restObj._timeFrames[0]._repeatYearly;
  9190. }
  9191. }
  9192.  
  9193. if((roFromTime === null) && (roToTime === null))
  9194. {
  9195. roFromTime = "0:00";
  9196. roToTime = "23:59";
  9197. roAllDay = true;
  9198. }
  9199.  
  9200. let hasExpired = false;
  9201. let isFuture = false;
  9202. let tNow = Date.now();
  9203. let tFrom = null;
  9204. let tTo = null;
  9205. if(roFromDate !== null)
  9206. {
  9207. tFrom = new Date(roFromDate + " " + roFromTime);
  9208. isFuture = (tFrom.getTime() > tNow);
  9209. }
  9210. if(roToDate !== null)
  9211. {
  9212. tTo = new Date(roToDate + " " + roToTime);
  9213. hasExpired = (tTo.getTime() < tNow);
  9214. }
  9215. if((hasExpired === true) && (roRepeats === true))
  9216. {
  9217. while(tTo.getTime() < tNow)
  9218. {
  9219. tFrom.setFullYear(tFrom.getFullYear() + 1);
  9220. tTo.setFullYear(tTo.getFullYear() + 1);
  9221. }
  9222. isFuture = (tFrom.getTime() > tNow);
  9223. hasExpired = false;
  9224. }
  9225.  
  9226.  
  9227. if(isFuture === true)
  9228. {
  9229. retval = '<tr bgcolor="#8080FF">';
  9230. }
  9231. else if(hasExpired === true)
  9232. {
  9233. retval = '<tr bgcolor="#FFFFC0">';
  9234. }
  9235. else
  9236. {
  9237. retval = '<tr>';
  9238. }
  9239.  
  9240. if(roDays === null)
  9241. {
  9242. roDays = 127;
  9243. }
  9244.  
  9245. retval += '<td style="text-align:center;">';
  9246. if((roDays & 1) == 1) retval += 'M';
  9247. else retval += '-';
  9248. retval += '</td><td style="text-align:center;">';
  9249. if((roDays & 2) == 2) retval += 'Tu';
  9250. else retval += '-';
  9251. retval += '</td><td style="text-align:center;">';
  9252. if((roDays & 4) == 4) retval += 'W';
  9253. else retval += '-';
  9254. retval += '</td><td style="text-align:center;">';
  9255. if((roDays & 8) == 8) retval += 'Th';
  9256. else retval += '-';
  9257. retval += '</td><td style="text-align:center;">';
  9258. if((roDays & 16) == 16) retval += 'F';
  9259. else retval += '-';
  9260. retval += '</td><td style="text-align:center;">';
  9261. if((roDays & 32) == 32) retval += 'Sa';
  9262. else retval += '-';
  9263. retval += '</td><td style="text-align:center;">';
  9264. if((roDays & 64) == 64) retval += 'Su';
  9265. else retval += '-';
  9266.  
  9267. retval += '</td><td nowrap style="text-align:center;">';
  9268.  
  9269. if(roFromDate === null) retval += 'All dates';
  9270. else retval += tFrom.toISOString().slice(0,10) + ' to ' + tTo.toISOString().slice(0,10);
  9271. if(roRepeats === true)
  9272. {
  9273. retval += '&nbsp;<i class="fa fa-repeat"> </i>';
  9274. }
  9275.  
  9276. retval += '</td><td nowrap style="text-align:center;">';
  9277.  
  9278. if((restObj._allDay === true) || (roAllDay === true)) retval += 'All day';
  9279. else retval += roFromTime + ' to ' + roToTime;
  9280.  
  9281. retval += '</td><td nowrap style="text-align:center;">';
  9282.  
  9283. retval += uroGetRestrictionLanes(restObj._disposition);
  9284.  
  9285. retval += '</td><td nowrap style="text-align:center;">';
  9286.  
  9287. retval += uroGetRestrictionLaneType(restObj._laneType);
  9288.  
  9289. retval += '</td><td nowrap style="text-align:center;">';
  9290.  
  9291. // for brevity, the popup only displays the allowed/prohibited restriction for the driveable vehicle types in the app...
  9292. let typesAllowed = [];
  9293. if((restObj._defaultType == "BLOCKED") || (restObj._defaultType == "TOLL"))
  9294. {
  9295. if(restObj._defaultType == "TOLL")
  9296. {
  9297. retval += I18n.lookup('restrictions.editing.segment.toll_road');
  9298. }
  9299. typesAllowed = uroGetRestrictionVehicleTypes(restObj, false, "FREE");
  9300. }
  9301. else
  9302. {
  9303. typesAllowed = uroGetRestrictionVehicleTypes(restObj, true, "BLOCKED");
  9304. }
  9305.  
  9306. let i;
  9307. for(i = 0; i < uroVehicleTypes.length; ++i)
  9308. {
  9309. if(typesAllowed[i] === true)
  9310. {
  9311. retval += '<i class="fa '+uroVehicleTypes[i][1]+'" style="color:#000000;"> </i>&nbsp;';
  9312. }
  9313. else
  9314. {
  9315. retval += '<i class="fa '+uroVehicleTypes[i][1]+'" style="color:#d0d0d0;"> </i>&nbsp;';
  9316. }
  9317. }
  9318.  
  9319. retval += '</td><td>';
  9320. retval += uroUtils.Clickify(restObj._description, '');
  9321. }
  9322.  
  9323. retval += '</td></tr>';
  9324.  
  9325. return retval;
  9326. }
  9327. function uroOpenURDialog(urID)
  9328. {
  9329. let t = {showNext: false, nextButtonString: I18n.lookup('problems.panel.done')};
  9330. let urObj = W.model.mapUpdateRequests.objects[urID];
  9331. W.reqres.request("problems:browse", _.extend(t, {problem: urObj}));
  9332. }
  9333. function uroRecentreSessionOnUR()
  9334. {
  9335. //uroGetMarker(uroLayers.ID.UR, uroShownFID).element.click();
  9336. uroOpenURDialog(uroShownFID);
  9337. W.map.moveTo(uroGetMarker(uroLayers.ID.UR, uroShownFID).lonlat, 17);
  9338. uroPopup.Hide();
  9339. return false;
  9340. }
  9341. function uroRecentreSessionOnMP()
  9342. {
  9343. uroGetMarker(uroLayers.ID.MP, uroShownFID).element.click();
  9344. W.map.moveTo(uroGetMarker(uroLayers.ID.MP, uroShownFID).lonlat, 17);
  9345. uroPopup.Hide();
  9346. return false;
  9347. }
  9348. function uroRecentreSessionOnPUR()
  9349. {
  9350. uroGetMarker(uroLayers.ID.PUR, uroShownFID).element.click();
  9351. W.map.moveTo(uroGetMarker(uroLayers.ID.PUR, uroShownFID).lonlat, 17);
  9352. uroPopup.Hide();
  9353. return false;
  9354. }
  9355. function uroRecentreSessionOnPPUR()
  9356. {
  9357. uroGetMarker(uroLayers.ID.PPUR, uroShownFID).element.click();
  9358. W.map.moveTo(uroGetMarker(uroLayers.ID.PPUR, uroShownFID).lonlat, 17);
  9359. uroPopup.Hide();
  9360. return false;
  9361. }
  9362. function uroRecentreSessionOnVenueNavPoint()
  9363. {
  9364. W.map.moveTo(uroGetVenueNavPoint(uroShownFID), 17);
  9365. uroPopup.Hide();
  9366. return false;
  9367. }
  9368. function uroStackListObj(fid,x,y)
  9369. {
  9370. this.fid = fid;
  9371. this.x = uroUtils.TypeCast(x);
  9372. this.y = uroUtils.TypeCast(y);
  9373. }
  9374. function uroRestackMarkers()
  9375. {
  9376. if(uroStackList.length === 0) return;
  9377.  
  9378. if(uroLayers.layers[uroStackType].mf !== null)
  9379. {
  9380. uroDBG.AddLog('restacking markers...');
  9381. // strip off the .realX/realY attributes from any UR object we've previously added it to, to allow
  9382. // the native recentering to work again...
  9383. let idList = uroGetMarkerIDs(uroStackType);
  9384. for(let marker of idList)
  9385. {
  9386. let testMarkerAttributes = uroGetAttributes(uroStackType, marker);
  9387. if(testMarkerAttributes.geometry.realX != null)
  9388. {
  9389. testMarkerAttributes.geometry.x = testMarkerAttributes.geometry.realX;
  9390. testMarkerAttributes.geometry.y = testMarkerAttributes.geometry.realY;
  9391. delete(testMarkerAttributes.geometry.realX);
  9392. delete(testMarkerAttributes.geometry.realY);
  9393. }
  9394. }
  9395. // now restack any markers that were repositioned...
  9396. for(let idx=0; idx<uroStackList.length; idx++)
  9397. {
  9398. let orig_x = uroStackList[idx].x + 'px';
  9399. let orig_y = uroStackList[idx].y + 'px';
  9400. let fid = uroStackList[idx].fid;
  9401.  
  9402. if(uroGetMarker(uroStackType, fid) != null)
  9403. {
  9404. uroGetMarker(uroStackType, fid).element.style.left = orig_x;
  9405. uroGetMarker(uroStackType, fid).element.style.top = orig_y;
  9406. }
  9407. }
  9408. uroStackList = [];
  9409. uroUnstackedMasterID = null;
  9410. uroStackType = null;
  9411. uroDBG.AddLog('...stacked!');
  9412. }
  9413. }
  9414. function uroIsIDAlreadyUnstacked(idSrc)
  9415. {
  9416. if(uroStackList.length === 0) return false;
  9417. for(let idx=0; idx<uroStackList.length; idx++)
  9418. {
  9419. if(uroStackList[idx].fid == idSrc) return true;
  9420. }
  9421. return false;
  9422. }
  9423. function uroCheckStacking(stackType, masterID, unstackedX, unstackedY)
  9424. {
  9425. //// WIP
  9426. return;
  9427. if(typeof(masterID) === 'number')
  9428. {
  9429. masterID = masterID.toString();
  9430. }
  9431.  
  9432. if(uroIsIDAlreadyUnstacked(masterID) === true) return;
  9433. if(uroStackType !== null) return;
  9434.  
  9435. uroDBG.AddLog('checking for marker stack, masterID: '+masterID+', stackType: '+stackType);
  9436. let stackList = [];
  9437. stackList.push(masterID);
  9438. let threshSquared = uroUtils.GetElmValue('_inputUnstackSensitivity');
  9439. threshSquared *= threshSquared;
  9440.  
  9441. let marker;
  9442.  
  9443. let offset = 0.000000001;
  9444. if(uroLayers.layers[stackType].mf !== null)
  9445. {
  9446. let idList = uroGetMarkerIDs(stackType);
  9447. let showOpen = true;
  9448. let showClosed = false;
  9449. let showTypes = null;
  9450. if(stackType === uroLayers.ID.UR)
  9451. {
  9452. showTypes = W?.issueTrackerController?.app?.attributes?.issueTrackerFilter?.attributes?.mapUpdateRequestsFilter?.attributes?.status;
  9453. }
  9454. else if(stackType === uroLayers.ID.MP)
  9455. {
  9456. showTypes = W?.issueTrackerController?.app?.attributes?.issueTrackerFilter?.attributes?.mapProblemsFilter?.attributes?.status;
  9457. }
  9458. if(showTypes !== null)
  9459. {
  9460. showOpen = ((showTypes == 'OPEN') || (showTypes == 'BOTH'));
  9461. showClosed = ((showTypes == 'CLOSED') || (showTypes == 'BOTH'));
  9462. }
  9463.  
  9464. for(marker of idList)
  9465. {
  9466. let testMarkerObj = uroGetMarker(stackType, marker);
  9467. let testMarkerAttributes = uroGetAttributes(stackType, marker);
  9468. if((testMarkerAttributes !== null) && (testMarkerObj !== null))
  9469. {
  9470. // if multiple markers are stacked exactly on top of one another, WME will always open up the one which it would have rendered on the
  9471. // top of the stack in the absence of any URO+ filtering, regardless of which UR pin actually receives the click event. To prevent
  9472. // this, we give each pin in the stack a unique set of false coordinates, storing the original coordinates in newly created
  9473. // properties so they can be restored later on.
  9474. //
  9475. // As of 3.169, the offset added to create the false coordinates has now been changed to a fractional value as opposed to the large integer
  9476. // it previously was, as the latter method has now been seen to cause problems with displaying the "A" marker when a road closure request MP
  9477. // is being viewed. By using a small fractional offset added to each stacked marker, the risk of accidentally setting the offset coords to
  9478. // those of another marker is low
  9479. if(testMarkerAttributes.geometry.realX === undefined)
  9480. {
  9481. testMarkerAttributes.geometry.realX = testMarkerAttributes.geometry.x;
  9482. testMarkerAttributes.geometry.x += offset;
  9483. testMarkerAttributes.geometry.realY = testMarkerAttributes.geometry.y;
  9484. testMarkerAttributes.geometry.y += offset;
  9485. offset += 0.000000001;
  9486. }
  9487.  
  9488. let includeInStack = (testMarkerObj.element.style.visibility != 'hidden');
  9489. let isClosed = testMarkerObj.element.classList.contains("recently-closed");
  9490. includeInStack = includeInStack && ((isClosed && showClosed) || (!isClosed && showOpen));
  9491. if(includeInStack)
  9492. {
  9493. if(testMarkerAttributes.id != masterID)
  9494. {
  9495. let bcr = testMarkerObj.element.getBoundingClientRect();
  9496. let xdiff = unstackedX - bcr.x;
  9497. let ydiff = unstackedY - bcr.y;
  9498. let distSquared = ((xdiff * xdiff) + (ydiff * ydiff));
  9499. if(distSquared < threshSquared)
  9500. {
  9501. stackList.push(testMarkerAttributes.id);
  9502. }
  9503. }
  9504. }
  9505. }
  9506. }
  9507. }
  9508.  
  9509. let inhibitUnstacking = (W.map.getZoom() < uroUtils.GetElmValue('_inputUnstackZoomLevel'));
  9510. inhibitUnstacking = inhibitUnstacking || (stackList.length < 2);
  9511.  
  9512. if(inhibitUnstacking == false)
  9513. {
  9514. uroStackType = stackType;
  9515. if(uroUnstackedMasterID != masterID)
  9516. {
  9517. uroDBG.AddLog('unstacked ID mismatch, relocating markers...');
  9518. uroRestackMarkers();
  9519. uroUnstackedMasterID = masterID;
  9520. uroStackList = [];
  9521.  
  9522. // push the highlighted marker onto the stacklist so uroIsIDAlreadyUnstacked() will return true
  9523. uroStackList.push(new uroStackListObj(masterID,unstackedX,unstackedY));
  9524.  
  9525. for(let shoveIdx=0; shoveIdx < stackList.length; shoveIdx++)
  9526. {
  9527. let fid = stackList[shoveIdx];
  9528. let stackMarker = uroGetMarker(stackType, fid);
  9529. if(stackMarker !== null)
  9530. {
  9531. let x = uroUtils.ParsePxString(stackMarker.element.style.left);
  9532. let y = uroUtils.ParsePxString(stackMarker.element.style.top);
  9533. // store the unstacked marker positions so they can be reinstated later
  9534. uroStackList.push(new uroStackListObj(fid,x,y));
  9535. stackMarker.element.style.left = unstackedX + 'px';
  9536. stackMarker.element.style.top = unstackedY + 'px';
  9537. unstackedX += 5;
  9538. unstackedY -= 20;
  9539. }
  9540. }
  9541.  
  9542.  
  9543. // hide other markers to prevent confusion with the unstacked markers
  9544. let listIDs = uroGetMarkerIDs(stackType);
  9545. for(marker in listIDs)
  9546. {
  9547. if(listIDs.hasOwnProperty(marker))
  9548. {
  9549. let toHideMarker = uroGetMarker(stackType, marker);
  9550. if(toHideMarker !== null)
  9551. {
  9552. let toHideID = toHideMarker.id;
  9553. if(uroIsIDAlreadyUnstacked(toHideID) === false)
  9554. {
  9555. toHideMarker = uroGetMarker(stackType, toHideID);
  9556. if(toHideMarker !== null)
  9557. {
  9558. toHideMarker.element.style.visibility = 'hidden';
  9559. }
  9560. }
  9561. }
  9562. }
  9563. }
  9564. }
  9565. }
  9566. else
  9567. {
  9568. uroRestackMarkers();
  9569. }
  9570. }
  9571. function uroGetVenueNavPoint(uroFID)
  9572. {
  9573. let retval = W.map.getUnprojectedCenter(); // allow the function to return a safe value in case we can't find the requested venue object...
  9574.  
  9575. let vObj = W.model.venues.objects[uroFID];
  9576. if(vObj !== undefined)
  9577. {
  9578. if(vObj.attributes.entryExitPoints.length > 0)
  9579. {
  9580. // if the venue has any navpoints defined, use the position of the first one
  9581. let tPoint = vObj.attributes.entryExitPoints[0].getPoint();
  9582. retval.lon = tPoint.coordinates[0];
  9583. retval.lat = tPoint.coordinates[1];
  9584. }
  9585. else
  9586. {
  9587. // otherwise use the centrepoint of the venue point or polygon
  9588. let tPoint = vObj.attributes.geometry.getCentroid();
  9589. let tLL = new OpenLayers.LonLat();
  9590. tLL.lon = tPoint.x;
  9591. tLL.lat = tPoint.y;
  9592. tLL = uroUtils.ConvertMercatorToWGS84(tLL);
  9593. retval.lon = tLL.lon;
  9594. retval.lat = tLL.lat;
  9595. }
  9596. }
  9597. return retval;
  9598. }
  9599. function uroOpenNewTab()
  9600. {
  9601. // flush the current settings into localStorage before the new tab opens, so that when its instance of
  9602. // URO+ fires up it'll have the same settings as this one
  9603. uroConfig.SaveSettings();
  9604. return true;
  9605. }
  9606. function uroGetRTCDuration(rcObj)
  9607. {
  9608. let duration = new Date(rcObj.attributes.endDate) - new Date(rcObj.attributes.startDate);
  9609. return Math.floor(duration / 86400000);
  9610. }
  9611. function uroGetRTCOffset(rcDate)
  9612. {
  9613. let dateObj = new Date(rcDate);
  9614. return (0 - uroUtils.DateToDays(dateObj));
  9615. }
  9616. function uroGetRTCOrigin(rcObj)
  9617. {
  9618. let retval = uroEnums.TRTC.UNKNOWN;
  9619.  
  9620. if(rcObj !== undefined)
  9621. {
  9622. if(rcObj.attributes.createdBy == -5)
  9623. {
  9624. retval = uroEnums.TRTC.WAZEFEED;
  9625. }
  9626. else if((W.model.users.objects[rcObj.attributes.createdBy] !== undefined) && (W.model.users.objects[rcObj.attributes.createdBy].attributes.rank == 6))
  9627. {
  9628. retval = uroEnums.TRTC.WAZEOTHER;
  9629. }
  9630. else
  9631. {
  9632. retval = uroEnums.TRTC.WME;
  9633. }
  9634. }
  9635.  
  9636. return retval;
  9637. }
  9638. function uroGetRTCState(rcObj)
  9639. {
  9640. let retval = uroEnums.SRTC.UNKNOWN;
  9641. let rcStatus = rcObj.attributes.closureStatus;
  9642.  
  9643. if(rcStatus !== undefined)
  9644. {
  9645. if(rcStatus === "ACTIVE")
  9646. {
  9647. retval = uroEnums.SRTC.ACTIVE;
  9648. }
  9649. else if(rcStatus === "NOT_STARTED")
  9650. {
  9651. retval = uroEnums.SRTC.FUTURE;
  9652. }
  9653. else if(rcStatus.indexOf("FINISHED") != -1)
  9654. {
  9655. retval = uroEnums.SRTC.EXPIRED;
  9656. }
  9657. // Haven't seen one of these yet, so assuming it should be treated
  9658. // the same as an expired closure...
  9659. else if(rcStatus === "SUSPENDED")
  9660. {
  9661. retval = uroEnums.SRTC.EXPIRED;
  9662. }
  9663. }
  9664.  
  9665. return retval;
  9666. }
  9667. function uroGetRTCStateText(rcObj)
  9668. {
  9669. let retval = "---";
  9670. let i18 = I18n.lookup("closures.statuses")[rcObj.attributes.closureStatus];
  9671. if(i18 !== undefined)
  9672. {
  9673. retval = i18;
  9674. }
  9675. return retval;
  9676. }
  9677. function uroGetAddress(streetID, houseNumber, formatForSegmentPopup, formatForNodePopup, showAsToll)
  9678. {
  9679. let result = '';
  9680. if((houseNumber !== undefined) && (houseNumber !== null))
  9681. {
  9682. result += houseNumber + ' ';
  9683. }
  9684.  
  9685. if(streetID != null)
  9686. {
  9687. let streetName = I18n.lookup('edit.address.no_street');
  9688. let doesStreetIDExist = true;
  9689. if(W.model.streets.objects[streetID] === undefined)
  9690. {
  9691. streetName = 'non-existent streetID';
  9692. doesStreetIDExist = false;
  9693. }
  9694. else
  9695. {
  9696. if((streetName !== null) && (W.model.streets.objects[streetID].attributes.isEmpty === false))
  9697. {
  9698. streetName = W.model.streets.objects[streetID].attributes.name;
  9699. }
  9700. }
  9701. if(formatForSegmentPopup === true)
  9702. {
  9703. if(showAsToll == true)
  9704. {
  9705. result += '<i class="fa fa-credit-card"></i> ';
  9706. }
  9707. result += '<b>'+streetName+'</b><br>';
  9708. }
  9709. else
  9710. {
  9711. result += streetName + ', ';
  9712. }
  9713.  
  9714. if(doesStreetIDExist === true)
  9715. {
  9716. let cityName = I18n.lookup('edit.address.no_city');
  9717. let doesCityIDExist = true;
  9718. let cityID = W.model.streets.objects[streetID].attributes.cityID;
  9719. if(W.model.cities.objects[cityID] === undefined)
  9720. {
  9721. cityName = 'non-existent cityID';
  9722. doesCityIDExist = false;
  9723. }
  9724. else
  9725. {
  9726. if(W.model.cities.objects[cityID].attributes.name !== "")
  9727. {
  9728. cityName = W.model.cities.objects[cityID].attributes.name;
  9729. }
  9730. }
  9731. result += cityName + ', ';
  9732.  
  9733. if(doesCityIDExist === true)
  9734. {
  9735. let stateID = W.model.cities.objects[cityID].attributes.stateID;
  9736. if(W.model.states.objects[stateID] === undefined)
  9737. {
  9738. result += 'non-existent stateID';
  9739. }
  9740. else
  9741. {
  9742. result += W.model.states.objects[stateID].attributes.name;
  9743. }
  9744. }
  9745. }
  9746. }
  9747. result += '<br>';
  9748.  
  9749. return result;
  9750. }
  9751. function uroGetSelectedSegmentRTCs(segID)
  9752. {
  9753. let closureTypes = uroEnums.DRTC.NONE;
  9754. let RTCObjs = [];
  9755. let selectedSegs = [];
  9756.  
  9757. if(segID === null)
  9758. {
  9759. // segID should always be set to a valid segment ID if we're being called from the segment mouseover
  9760. // handler, so if it's null it implies we've instead been called from the closure panel handler where
  9761. // we might therefore be dealing with a multi-segment selection...
  9762. selectedSegs = W.selectionManager.getSegmentSelection().segments;
  9763. }
  9764.  
  9765. if((selectedSegs.length > 0) || (segID !== null))
  9766. {
  9767. for(let roadClosure in W.model.roadClosures.objects)
  9768. {
  9769. if(W.model.roadClosures.objects.hasOwnProperty(roadClosure))
  9770. {
  9771. let rcObj = W.model.roadClosures.objects[roadClosure];
  9772. rcObj.segIDs = [rcObj.attributes.segID]; // copy the segID property into an array so we can push extra segIDs into it later...
  9773.  
  9774. // set a direction value corresponding to the A-B or B-A setting - if we later end up combining an A-B and B-A closure
  9775. // into a two-way closure, we can then change the direction value to indicate this as well
  9776. if(rcObj.attributes.forward === true)
  9777. {
  9778. rcObj.direction = uroEnums.DRTC.SEG_AB;
  9779. }
  9780. else
  9781. {
  9782. rcObj.direction = uroEnums.DRTC.SEG_BA;
  9783. }
  9784.  
  9785. // for each of the selected or moused-over segments, find all the closures which have matching segIDs
  9786. if(segID !== null)
  9787. {
  9788. if(rcObj.attributes.segID == segID)
  9789. {
  9790. RTCObjs.push(rcObj);
  9791. }
  9792. }
  9793. else
  9794. {
  9795. for(let i = 0; i < selectedSegs.length; ++i)
  9796. {
  9797. if(rcObj.attributes.segID == selectedSegs[i].attributes.id)
  9798. {
  9799. RTCObjs.push(rcObj);
  9800. break;
  9801. }
  9802. }
  9803. }
  9804. }
  9805. }
  9806.  
  9807. // RTCObjs now contains all of the segment closures relating to all of the segments of interest, so
  9808. // we can begin to organise them such that by the time we exit this function, the array will then contain
  9809. // an optimised list of closures that matches up to the list shown in the closure sidepanel, taking into
  9810. // account closures applying to all segments vs some, closures that can be merged into two-ways etc.
  9811.  
  9812. // first sort the closure by their start date, with a secondary sort by direction for those closures
  9813. // that have the same start date
  9814. RTCObjs = RTCObjs.sort(function(a,b)
  9815. {
  9816. if(a.attributes.startDate === b.attributes.startDate)
  9817. {
  9818. if(a.direction == uroEnums.DRTC.SEG_AB) return -1;
  9819. return 1;
  9820. }
  9821. if(a.attributes.startDate > b.attributes.startDate) return 1;
  9822. return -1;
  9823. });
  9824.  
  9825. // if we've got at least two closures in the sorted list, we then test adjacent list entries
  9826. // to see if they contain closure details which are identical except for their segment IDs, and
  9827. // combine them if so
  9828. if(RTCObjs.length > 1)
  9829. {
  9830. let i = 0;
  9831. while(i < (RTCObjs.length - 1))
  9832. {
  9833. if(
  9834. (RTCObjs[i].attributes.createdBy == RTCObjs[i+1].attributes.createdBy) &&
  9835. (RTCObjs[i].attributes.endDate == RTCObjs[i+1].attributes.endDate) &&
  9836. (RTCObjs[i].attributes.eventId == RTCObjs[i+1].attributes.eventId) &&
  9837. (RTCObjs[i].attributes.location == RTCObjs[i+1].attributes.location) &&
  9838. (RTCObjs[i].attributes.reason == RTCObjs[i+1].attributes.reason) &&
  9839. (RTCObjs[i].attributes.startDate == RTCObjs[i+1].attributes.startDate) &&
  9840. (RTCObjs[i].direction == RTCObjs[i+1].direction)
  9841. )
  9842. {
  9843. RTCObjs[i].segIDs.push(RTCObjs[i+1].attributes.segID);
  9844. RTCObjs.splice(i+1, 1);
  9845. }
  9846. else
  9847. {
  9848. ++i;
  9849. }
  9850. }
  9851. }
  9852.  
  9853. // after that first trimming of the list, if there are still two or more entries then
  9854. // we perform a second pass, this time merging any adjacent entries which have the same
  9855. // segment IDs in their segIDs arrays - these are two-way closures applying to all those
  9856. // segments, and so we also change the direction value to indicate two-way vs A-B or B-A
  9857. if(RTCObjs.length > 1)
  9858. {
  9859. let i = 0;
  9860. while(i < (RTCObjs.length - 1))
  9861. {
  9862. if
  9863. (
  9864. (RTCObjs[i].segIDs.sort().join(',') == RTCObjs[i+1].segIDs.sort().join(',')) &&
  9865. (RTCObjs[i].attributes.createdBy == RTCObjs[i+1].attributes.createdBy) &&
  9866. (RTCObjs[i].attributes.endDate == RTCObjs[i+1].attributes.endDate) &&
  9867. (RTCObjs[i].attributes.eventId == RTCObjs[i+1].attributes.eventId) &&
  9868. (RTCObjs[i].attributes.location == RTCObjs[i+1].attributes.location) &&
  9869. (RTCObjs[i].attributes.reason == RTCObjs[i+1].attributes.reason) &&
  9870. (RTCObjs[i].attributes.startDate == RTCObjs[i+1].attributes.startDate)
  9871. )
  9872. {
  9873. RTCObjs[i].direction = uroEnums.DRTC.SEG_BI;
  9874. RTCObjs.splice(i+1, 1);
  9875. }
  9876. ++i;
  9877. }
  9878. }
  9879. }
  9880.  
  9881. let RTTCObjs = [];
  9882. if(segID !== null)
  9883. {
  9884. // If we've been called from the segment popup handler, we now also check for any turn closures
  9885. // associated with this segment, so that their details can also be shown in the popup...
  9886. for(let turnClosure in W.model.turnClosures.objects)
  9887. {
  9888. if(W.model.turnClosures.objects.hasOwnProperty(turnClosure))
  9889. {
  9890. let tcObj = W.model.turnClosures.objects[turnClosure];
  9891. if(tcObj.attributes.fromSegID === segID)
  9892. {
  9893. tcObj.direction = uroEnums.DRTC.TURN_OUT;
  9894. RTTCObjs.push(tcObj);
  9895. }
  9896. else if(tcObj.attributes.toSegID === segID)
  9897. {
  9898. tcObj.direction = uroEnums.DRTC.TURN_IN;
  9899. RTTCObjs.push(tcObj);
  9900. }
  9901. }
  9902. }
  9903.  
  9904. if(RTTCObjs.length > 1)
  9905. {
  9906. RTTCObjs = RTTCObjs.sort(function(a,b)
  9907. {
  9908. if(a.attributes.startDate === b.attributes.startDate)
  9909. {
  9910. if(a.direction == uroEnums.DRTC.TURN_OUT) return -1;
  9911. return 1;
  9912. }
  9913. if(a.attributes.startDate > b.attributes.startDate) return 1;
  9914. return -1;
  9915. });
  9916. }
  9917. }
  9918.  
  9919. uroRTCObjs = RTCObjs.concat(RTTCObjs);
  9920. for(let i = 0; i < uroRTCObjs.length; ++i)
  9921. {
  9922. closureTypes |= uroRTCObjs[i].direction;
  9923. }
  9924.  
  9925. // the closure list ordering at this point doesn't always match up to the order used by the closures panel when
  9926. // a mixture of "all segment" and "some segment" closures are present - need to work out what ordering rules
  9927. // WME is using here...
  9928. return closureTypes;
  9929. }
  9930. function uroGetLengthString(length)
  9931. {
  9932. let retval = '';
  9933. if(length == null)
  9934. {
  9935. retval = "Default";
  9936. }
  9937. else if(W.model.isImperial == true)
  9938. {
  9939. retval = (length / (12 * 2.54)).toFixed(1) + "ft";
  9940. }
  9941. else
  9942. {
  9943. retval = (length / 100).toFixed(1) + "m";
  9944. }
  9945.  
  9946. return retval;
  9947. }
  9948. function uroGetHighlightedMapFeature()
  9949. {
  9950. let featureID = W.selectionManager.mouseInFeature;
  9951. let retval = null;
  9952.  
  9953. if(featureID !== undefined)
  9954. {
  9955. let isSelected = W.selectionManager.isSelected(featureID);
  9956. if(isSelected === false)
  9957. {
  9958. retval = W.selectionManager.getObjectByFeatureId(featureID);
  9959. }
  9960. }
  9961. return retval;
  9962. }
  9963. function uroGetFeatureRenderIntent(moObj)
  9964. {
  9965. let retval = "unknown";
  9966.  
  9967. if(moObj !== null)
  9968. {
  9969. let isSelected = moObj.selected;
  9970.  
  9971. if(isSelected === true)
  9972. {
  9973. retval = "highlightselected";
  9974. }
  9975. else
  9976. {
  9977. retval = "highlight";
  9978. }
  9979. }
  9980.  
  9981. return retval;
  9982. }
  9983. function uroExclusiveCB()
  9984. {
  9985. let cbChecked = uroUtils.GetCBChecked(this.id);
  9986.  
  9987. if(cbChecked === true)
  9988. {
  9989. let pairedList = this.attributes.pairedWith.value.split(',');
  9990. for(let i=0; i<pairedList.length; i++)
  9991. {
  9992. uroUtils.SetCBChecked(pairedList[i], false);
  9993. }
  9994. }
  9995. }
  9996. function uroContainsPoint(geo, point)
  9997. {
  9998. let retval = false;
  9999. try
  10000. {
  10001. let j = 1;
  10002. for(let i = 0; i < geo.length; ++i)
  10003. {
  10004. if
  10005. (
  10006. ((point[1] >= geo[i][1]) && (point[1] <= geo[j][1])) ||
  10007. ((point[1] >= geo[j][1]) && (point[1] <= geo[i][1]))
  10008. )
  10009. {
  10010. let lx = geo[i][0];
  10011. if(geo[i][1] != geo[j][1])
  10012. {
  10013. let g = ((point[1] - geo[i][1]) / (geo[j][1] - geo[i][1]));
  10014. lx += (g * (geo[j][0] - geo[i][0]));
  10015. }
  10016.  
  10017. if(point[0] <= lx)
  10018. {
  10019. retval = !retval;
  10020. }
  10021. }
  10022. if(++j == geo.length)
  10023. {
  10024. j = 0;
  10025. }
  10026. }
  10027. }
  10028. catch
  10029. {
  10030. }
  10031. return retval;
  10032. }
  10033. function uroGetAMs(e)
  10034. {
  10035. if(uroMTEMode) return;
  10036. if(!uroFilterPreamble) return;
  10037. if(!uroInit.initialised) return;
  10038. if(document.getElementById("uroAMList") == null) return;
  10039. if(document.getElementsByClassName('topbar') == null) return;
  10040.  
  10041. if(uroUtils.GetCBChecked("_cbMoveAMList") === false)
  10042. {
  10043. document.getElementsByClassName('area-managers-region')[0].style.display = "block";
  10044. uroAMList.innerHTML = uroUtils.ModifyHTML("");
  10045. document.getElementsByClassName('topbar')[0].style.backgroundColor=null;
  10046. return;
  10047. }
  10048.  
  10049. document.getElementsByClassName('topbar')[0].style.backgroundColor="#000000";
  10050. document.getElementsByClassName('area-managers-region')[0].style.display = "none";
  10051.  
  10052. let amList = '';
  10053. let tName = '';
  10054. if(W.map.managedAreasLayer.getVisibility() === true)
  10055. {
  10056. let mouseX = e.pageX - document.getElementById('map').getBoundingClientRect().left;
  10057. let mouseY = e.pageY - document.getElementById('map').getBoundingClientRect().top;
  10058. let mousePixel = W.map.getLonLatFromPixel(new OpenLayers.Pixel(mouseX, mouseY));
  10059. let mousePoint = [];
  10060. mousePoint.push(mousePixel.lon);
  10061. mousePoint.push(mousePixel.lat);
  10062.  
  10063. for(let amObj in W.model.managedAreas.objects)
  10064. {
  10065. let nc = W.model.managedAreas.objects[amObj].attributes.geoJSONGeometry.coordinates.length;
  10066. for(let i = 0; i < nc; ++i)
  10067. {
  10068. let geo = W.model.managedAreas.objects[amObj].attributes.geoJSONGeometry.coordinates[i];
  10069. if(nc > 1)
  10070. {
  10071. geo = geo[0];
  10072. }
  10073.  
  10074. if(uroContainsPoint(geo, mousePoint) === true)
  10075. {
  10076. let amName = uroUtils.GetUserNameFromID(W.model.managedAreas.objects[amObj].attributes.userID);
  10077. if(amList.indexOf(amName) === -1)
  10078. {
  10079. if(amList !== '') amList += ', ';
  10080. tName = uroUtils.GetUserNameAndRank(W.model.managedAreas.objects[amObj].attributes.userID);
  10081. if(tName.indexOf('a href') !== -1)
  10082. {
  10083. tName = tName.replace('a href', 'a style="color:#c0c0ff;" href');
  10084. }
  10085. amList += tName;
  10086. }
  10087. }
  10088. }
  10089. }
  10090. if(amList === '')
  10091. {
  10092. amList = 'none';
  10093. }
  10094. amList = "&nbsp;-&nbsp;<b>Area Managers:</b> "+amList;
  10095. }
  10096. document.getElementById("uroAMList").innerHTML = uroUtils.ModifyHTML(amList);
  10097. }
  10098. function uroNewTabAtMouseLoc(x, y)
  10099. {
  10100. let tPix = new OpenLayers.Pixel(x,y);
  10101. let mPos = uroUtils.ConvertMercatorToWGS84(W.map.getLonLatFromPixel(tPix));
  10102. let nZoom = W.map.getZoom();
  10103. if(nZoom < 17) nZoom = 17;
  10104. let nHref = window.location.origin + window.location.pathname;
  10105. nHref += '?lon=' + mPos.lon;
  10106. nHref += '&lat=' + mPos.lat;
  10107. nHref += '&zoomLevel=' + nZoom;
  10108. window.open(nHref);
  10109. }
  10110. function uroMouseDown(e)
  10111. {
  10112. uroMouseIsDown = true;
  10113. if((e.altKey === true) && (e.ctrlKey === true))
  10114. {
  10115. uroNewTabAtMouseLoc(e.offsetX, e.offsetY);
  10116. }
  10117. }
  10118. function uroMouseUp()
  10119. {
  10120. uroMouseIsDown = false;
  10121. }
  10122. function uroTestPointerOutsideMap(mX, mY)
  10123. {
  10124. let mapElm = document.getElementById("map");
  10125. if(mapElm === undefined) return;
  10126. let mapBCR = mapElm.getBoundingClientRect();
  10127.  
  10128. if
  10129. (
  10130. (mX < mapBCR.left) ||
  10131. (mX > mapBCR.right) ||
  10132. (mY < mapBCR.top) ||
  10133. (mY > mapBCR.bottom)
  10134. )
  10135. {
  10136. if(uroUtils.GetCBChecked('_cbKillInertialPanning') === true)
  10137. {
  10138. let controller = null;
  10139. if (W.map.navigationControl)
  10140. {
  10141. controller = W.map.navigationControl;
  10142. }
  10143. else if(W.map.controls.find(control => control.CLASS_NAME == 'OpenLayers.Control.Navigation'))
  10144. {
  10145. controller = W.map.controls.find(control => control.CLASS_NAME == 'OpenLayers.Control.Navigation');
  10146. }
  10147. if (controller !== null)
  10148. {
  10149. controller.dragPan.panMapStart();
  10150. }
  10151. }
  10152. return true;
  10153. }
  10154. else
  10155. {
  10156. return false;
  10157. }
  10158. }
  10159. function uroMouseOut(e)
  10160. {
  10161. if(uroTestPointerOutsideMap(e.clientX, e.clientY))
  10162. {
  10163. uroPopup.Hide();
  10164. }
  10165. }
  10166. function uroUREvent_onObjectsAdded()
  10167. {
  10168. if(uroUtils.GetCBChecked('_cbURResolverIDFilter') === true)
  10169. {
  10170. uroUpdateEditorList(W.model.mapUpdateRequests.objects, '_selectURResolverID', false, false, true, false);
  10171. }
  10172. if(uroPopulatingRequestSessions === false)
  10173. {
  10174. uroFilterURs();
  10175. }
  10176. }
  10177. function uroGetSelectedURCommentCount()
  10178. {
  10179. if(W.model.updateRequestSessions.objects[uroSelectedURID] != null)
  10180. {
  10181. let cachedCommentCount = W.model.updateRequestSessions.objects[uroSelectedURID].attributes.comments.length;
  10182. uroDBG.AddLog(uroSelectedURID+':'+cachedCommentCount+' '+uroExpectedCommentCount);
  10183.  
  10184. // if there aren't the same number of cached comments as there are comments in the UR dialog list, initiate
  10185. // a refresh of the comment data...
  10186. if(cachedCommentCount != uroExpectedCommentCount)
  10187. {
  10188. if(uroPendingCommentDataRefresh === true)
  10189. {
  10190. if(cachedCommentCount > 0)
  10191. {
  10192. uroCachedLastCommentID = W.model.updateRequestSessions.objects[uroSelectedURID].attributes.comments[cachedCommentCount-1].id;
  10193. }
  10194. else
  10195. {
  10196. uroCachedLastCommentID = null;
  10197. }
  10198. uroDBG.AddLog('updateRequestSessions refresh required for UR '+uroSelectedURID);
  10199. if(uroCachedLastCommentID !== null)
  10200. {
  10201. uroDBG.AddLog('last comment ID for this UR is '+uroCachedLastCommentID);
  10202. }
  10203. else
  10204. {
  10205. uroDBG.AddLog('first comment for this UR, no previous comment to ID');
  10206. }
  10207. let idList = [];
  10208. idList.push(uroSelectedURID);
  10209. // need to delete the existing cache object first, as .get() is only capable of creating new objects,
  10210. // it doesn't seem able to update an existing object with new data
  10211. W.model.updateRequestSessions.remove(W.model.updateRequestSessions.objects[uroSelectedURID]);
  10212. W.model.updateRequestSessions.getAsync(idList);
  10213.  
  10214. // the call to .get() initiates a XMLHttpRequest for the data, so we now need to switch modes - the
  10215. // refresh process has started so we're no longer pending, but we are now waiting for the XMLHttpRequest
  10216. // to return something...
  10217. uroPendingCommentDataRefresh = false;
  10218. uroWaitingCommentDataRefresh = true;
  10219. }
  10220. else
  10221. {
  10222. if(cachedCommentCount > 0)
  10223. {
  10224. let currentLastCommentID = W.model.updateRequestSessions.objects[uroSelectedURID].attributes.comments[cachedCommentCount-1].id;
  10225. if(currentLastCommentID == uroCachedLastCommentID)
  10226. {
  10227. // most recent comment loaded for this UR is the same one that was present at the start of this
  10228. // refresh process, so kick back into pending mode so we can retry the .get()...
  10229. uroDBG.AddLog('latest comment ID still the same, reverting to pending mode...');
  10230. uroPendingCommentDataRefresh = true;
  10231. }
  10232. else
  10233. {
  10234. // something may have gone awry here - the most recent comment loaded for this UR doesn't have the
  10235. // same ID as the one present at the start of the refresh process, yet the comment counts still don't
  10236. // match up, which suggests either a comment got lost along the way or someone else has commented on
  10237. // the same UR at almost the same time. To get out of the loop this would create, assume that a
  10238. // mismatch in the IDs means the .get() has completed successfully no matter what the new comment
  10239. // count is, and take this new count to be the count we were expecting all along...
  10240. uroDBG.AddLog('latest comment ID different, but expected count not correct...');
  10241. uroExpectedCommentCount = cachedCommentCount;
  10242. }
  10243. }
  10244. else
  10245. {
  10246. uroDBG.AddLog('first comment on this UR not received yet, reverting to pending mode...');
  10247. uroPendingCommentDataRefresh = true;
  10248. }
  10249. }
  10250.  
  10251. }
  10252. else
  10253. {
  10254. // if the WME session is loaded with a UR already selected, such that WME has opened the UR dialog as part
  10255. // of the session startup process, adding new comments to the UR cause the cached data to be updated immediately.
  10256. // This prevents URO+ from switching into waiting mode in the above block of code, so we have to instead do
  10257. // it here by comparing the cached count against the expected count following the Send click event.
  10258. if(cachedCommentCount >= uroExpectedCommentCount)
  10259. {
  10260. uroPendingCommentDataRefresh = false;
  10261. uroWaitingCommentDataRefresh = true;
  10262. uroExpectedCommentCount = null;
  10263. }
  10264.  
  10265. // once the cached data has been updated, refilter the URs so that the new comment count is taken into account
  10266. // immediately for filtering and display purposes
  10267. if(uroWaitingCommentDataRefresh === true)
  10268. {
  10269. uroWaitingCommentDataRefresh = false;
  10270. uroFilterURs();
  10271. uroDBG.AddLog('refresh complete');
  10272. }
  10273. }
  10274. }
  10275. }
  10276. function uroAddedComment()
  10277. {
  10278. // when the user clicks the Send button to submit a new UR comment, this event handler fires before the new comment is
  10279. // posted to the server and thus also before the comment list gets updated in the UR dialog. So we take the current
  10280. // comment count and, if the new comment edit box isn't empty, increment it by 1 to get the expected count. Then we
  10281. // set the pending flag true to initiate a session refresh on the next 100ms tick
  10282. uroExpectedCommentCount = W.model.updateRequestSessions.objects[uroSelectedURID].attributes.comments.length;
  10283. if(document.getElementsByClassName('new-comment-text')[0].value !== '')
  10284. {
  10285. uroExpectedCommentCount++;
  10286. uroDBG.AddLog('new comment added to UR '+uroSelectedURID+', cache refresh required...');
  10287. uroPendingCommentDataRefresh = true;
  10288. }
  10289. else
  10290. {
  10291. uroPendingCommentDataRefresh = false;
  10292. }
  10293. }
  10294. function uroInhibitNextUpdateRequestButton(e)
  10295. {
  10296. e.stopPropagation();
  10297.  
  10298. let doClick = true;
  10299. if(document.getElementsByClassName('form-control new-comment-text').length > 0)
  10300. {
  10301. if(document.getElementsByClassName('form-control new-comment-text')[0].textLength > 0)
  10302. {
  10303. uroAlertBox.Show("fa-warning", "URO+ Warning", "Comment not sent, close report panel anyway?", true, "Yes", "No", uroCloseReportPanel, null);
  10304. // set doClick to false here, as uroCloseReportPanel will be called by the alert box handler if required...
  10305. doClick = false;
  10306. }
  10307. }
  10308. // no alert box has been generated, so close the panel
  10309. if(doClick)
  10310. {
  10311. uroCloseReportPanel();
  10312. }
  10313. }
  10314. function uroCloseReportPanel()
  10315. {
  10316. document.getElementsByClassName('close-panel')[0].click();
  10317. }
  10318. function uroIncrementClosureDate(oldDate, incByDays)
  10319. {
  10320. // Thanks to WME no longer using consistent ISO8601 date formatting when displaying
  10321. // closure details, parsing the date string is now somewhat more involved. Thanks devs...
  10322.  
  10323. // Default to returning the existing date, just in case we can't increment it
  10324. let retval = oldDate;
  10325. let sepChar = null;
  10326.  
  10327. // Search through oldDate for a non-digit character, so we know what's
  10328. // being used to seperate the year, month and date values by this locale...
  10329. for(let i = 0; i < oldDate.length; ++i)
  10330. {
  10331. if((oldDate[i] < '0') || (oldDate[i] > '9'))
  10332. {
  10333. sepChar = oldDate[i];
  10334. break;
  10335. }
  10336. }
  10337.  
  10338. if(sepChar != null)
  10339. {
  10340. // First build a "probe" date object we can use to determine what the user's WME locale
  10341. // does with dates.
  10342. let incDate = new Date();
  10343. incDate.setFullYear(3000);
  10344. incDate.setMonth(10);
  10345. incDate.setDate(22);
  10346.  
  10347. // With these three carefully chosen date elements set, we now generate a localised datestring
  10348. // using the WME locale setting - the first character of which then tells us the date
  10349. // format - 1 = MDY (ack ptui), 2 = DMY (good), 3 = YMD (sweet!)
  10350. let localeDate = incDate.toLocaleDateString(I18n.locale);
  10351. let dateFormat = localeDate[0];
  10352.  
  10353. // Now we know the seperator character and the date format, so we can finally start to parse
  10354. // the existing date string...
  10355. let oldDateBits = oldDate.split(sepChar);
  10356. let datePos;
  10357. let monthPos;
  10358. let yearPos;
  10359. if(dateFormat == '1')
  10360. {
  10361. datePos = 1;
  10362. monthPos = 0;
  10363. yearPos = 2;
  10364. }
  10365. else if(dateFormat == '2')
  10366. {
  10367. datePos = 0;
  10368. monthPos = 1;
  10369. yearPos = 2;
  10370. }
  10371. else
  10372. {
  10373. datePos = 2;
  10374. monthPos = 1;
  10375. yearPos = 0;
  10376. }
  10377. incDate.setFullYear(parseInt(oldDateBits[yearPos]));
  10378. incDate.setMonth(parseInt(oldDateBits[monthPos]) - 1);
  10379. incDate.setDate(parseInt(oldDateBits[datePos]));
  10380. let tDate = new Date(incDate.getTime());
  10381. incDate.setDate(tDate.getDate() + incByDays);
  10382.  
  10383. retval = incDate.toLocaleDateString(I18n.locale);
  10384.  
  10385. // Except for those pesky locales where toLocaleDateString() doesn't *quite*
  10386. // return what WME is expecting. Because, you know, why make it easy for
  10387. // scripters when you can chuck in a few subtle curveballs like this, eh...
  10388.  
  10389. if(I18n.locale == 'bg')
  10390. {
  10391. // Remove the "r." suffix
  10392. retval = retval.split(" ")[0] + " ";
  10393. }
  10394. }
  10395. return retval;
  10396. }
  10397. function uroGetElementProperty(elmName, elmOffset, elmProperty)
  10398. {
  10399. let retval = null;
  10400. if(document.getElementsByName(elmName).length > elmOffset)
  10401. {
  10402. retval = document.getElementsByName(elmName)[elmOffset][elmProperty];
  10403. }
  10404. else if(document.getElementById(elmName) !== null)
  10405. {
  10406. retval = document.getElementById(elmName)[elmProperty];
  10407. }
  10408. return retval;
  10409. }
  10410. function uroGetShadowElementProperty(elmName, shadowElmType, property)
  10411. {
  10412. let retval = null;
  10413. let tObj = document.getElementById(elmName);
  10414. if(tObj !== null)
  10415. {
  10416. let sObj = tObj.shadowRoot.querySelector(shadowElmType);
  10417. if(sObj !== null)
  10418. {
  10419. retval = sObj[property];
  10420. }
  10421. }
  10422. return retval;
  10423. }
  10424. let rtcTotal = null;
  10425. let rtcSegIDs = null;
  10426. function uroScrollToEndOfClosures()
  10427. {
  10428. let cItems = document.querySelectorAll('.closure-item');
  10429. let nClosures = cItems.length;
  10430. let segIDs = uroUtils.GetSelectedSegmentIDs();
  10431. let sameSegs = false;
  10432. if((segIDs !== null) && (rtcSegIDs !== null))
  10433. {
  10434. if(segIDs.length == rtcSegIDs.length)
  10435. {
  10436. sameSegs = true;
  10437. for(let i = 0; i < segIDs.length; ++i)
  10438. {
  10439. if(segIDs[i] != rtcSegIDs[i])
  10440. {
  10441. sameSegs = false;
  10442. break;
  10443. }
  10444. }
  10445. }
  10446. }
  10447. if(sameSegs === false)
  10448. {
  10449. rtcTotal = null;
  10450. rtcSegIDs = segIDs;
  10451. }
  10452. if(nClosures > rtcTotal)
  10453. {
  10454. if(cItems[0].getBoundingClientRect().height != 0)
  10455. {
  10456. rtcTotal = nClosures;
  10457. if(uroUtils.GetCBChecked('_cbAutoScrollClosureList') == true)
  10458. {
  10459. // Scroll to the end of the closure tab, as that's where the closure you're most likely to be cloning
  10460. // is located...
  10461. cItems[cItems.length - 1].scrollIntoView();
  10462. }
  10463. }
  10464. }
  10465. }
  10466. function uroClosureEditUIChanged()
  10467. {
  10468. if(document.getElementsByClassName('edit-closure').length === 1)
  10469. {
  10470. // note: this also fires when the UI is closed, due to the change events triggered as its elements are removed
  10471. // prior to the tab itself closing...
  10472.  
  10473. let notReady = 0;
  10474. let mteDropDown = document.getElementById('closure_eventId');
  10475. if(document.getElementById('closure_reason').shadowRoot.querySelector('input') === null) notReady += 1;
  10476. if(document.getElementById('closure_direction').shadowRoot.querySelector('.selected-value') === null) notReady += 2;
  10477. else if(document.getElementById('closure_direction').shadowRoot.querySelector('.selected-value').innerText === "") notReady += 4;
  10478. if(document.getElementById('closure_startDate') === null) notReady += 8;
  10479. if(document.getElementById('closure_endDate') === null) notReady += 16;
  10480. if(mteDropDown.shadowRoot.querySelector('.selected-value') === null) notReady += 32;
  10481. else if(mteDropDown.shadowRoot.querySelector('.selected-value').innerText === "") notReady += 64;
  10482. if(document.getElementById('closure_permanent').shadowRoot.querySelector('.wz-checkbox') === null) notReady += 128;
  10483. if(notReady === 0)
  10484. {
  10485. if(uroRTCClone.PendingClone === -3)
  10486. {
  10487. uroRTCClone.Complete();
  10488. }
  10489. else if(uroRTCClone.PendingClone !== -1)
  10490. {
  10491. uroRTCClone.Copy();
  10492. }
  10493. else
  10494. {
  10495. uroFixMTEDropDown(mteDropDown);
  10496. }
  10497. }
  10498. }
  10499. else
  10500. {
  10501. if(uroRTCClone.PendingClone === -2)
  10502. {
  10503. // generate a click event on the Add a closure button to open up the closure editing UI, then
  10504. // wait for the UI to finish opening...
  10505. document.getElementsByClassName('add-closure-button')[0].click();
  10506. uroRTCClone.PendingClone = -3;
  10507. }
  10508. uroClosureListHandler();
  10509. }
  10510. }
  10511. function uroTSTPopupHandler()
  10512. {
  10513. if(document.getElementsByClassName('panel')[0] === undefined)
  10514. {
  10515. uroHidePopupOnPanelOpen = true;
  10516. }
  10517.  
  10518. if(uroPopup.shown === true)
  10519. {
  10520. let hidePopup = false;
  10521.  
  10522. if(document.getElementsByClassName('panel')[0] != null)
  10523. {
  10524. if(uroHidePopupOnPanelOpen === true)
  10525. {
  10526. hidePopup = true;
  10527. uroHidePopupOnPanelOpen = false;
  10528. }
  10529. }
  10530.  
  10531. if(hidePopup === true)
  10532. {
  10533. uroPopup.Hide();
  10534. }
  10535. }
  10536.  
  10537. if((uroAFN.hoverObj !== null) && (uroAFN.hoverTime != -1) && (uroAFN.overlayShown === false))
  10538. {
  10539. if(++uroAFN.hoverTime > 5)
  10540. {
  10541. uroAFN.OverlaySetup();
  10542. }
  10543. }
  10544. uroAFN.ReplaceAreaNames(false);
  10545.  
  10546. if(uroPopup.autoHideTimer > 0)
  10547. {
  10548. if(--uroPopup.autoHideTimer === 0)
  10549. {
  10550. uroPopup.Hide();
  10551. }
  10552. }
  10553.  
  10554. if(uroPopup.timer > 0)
  10555. {
  10556. if(uroPopup.mouseIn === false)
  10557. {
  10558. uroPopup.timer--;
  10559. }
  10560. }
  10561. if(uroPopup.timer === 0)
  10562. {
  10563. uroPopup.Hide();
  10564. }
  10565. }
  10566. function uroTSTNextBtnHandler()
  10567. {
  10568. // replaces the "next xxx" button on UR, MP and PUR editing UIs
  10569.  
  10570. // Correctly determining what WME is displaying for the "next" button in the UR/MP/(P)PUR panel is not trivial due to
  10571. // inconsistencies in the panel behaviour depending on whether it was opened by clicking directly on the relevant
  10572. // marker, or by clicking on the associated feed entry... For PURs, there's also the added complication of multi-part
  10573. // update requests, where the same marker/panel are used to access more than one request and where, therefore, we need
  10574. // to enable access to all requests contained within the PUR, but still inhibit the "next" button once the last
  10575. // request in the multi-part sequence has been viewed.
  10576. //
  10577. // For directly-accesed markers, the "next" button caption is:
  10578. //
  10579. // URs = "Next update request" (update_requests.panel.next)
  10580. // MPs = "Next map problem" (problems.panel.next)
  10581. // PURs = "Next place" for single-part PURs or for the last part of a multi-part PUR (venues.update_requests.panel.next_venue)
  10582. // = "Next" for all but the last part of a multi-part PUR (venues.update_requests.panel.next)
  10583. // PPURs = "Next place" (venues.update_requests.panel.next_venue)
  10584. //
  10585. // For markers accessed via the feed, the "next" button caption always appears to be "Next issue" (feed.issues.next)
  10586.  
  10587.  
  10588.  
  10589. let reportPanel = document.querySelector('#panel-container');
  10590.  
  10591. if(reportPanel.childElementCount > 0)
  10592. {
  10593. let nurButton = reportPanel.getElementsByClassName('next')[0];
  10594. if(nurButton === undefined)
  10595. {
  10596. nurButton = reportPanel.getElementsByClassName('next-venue')[0];
  10597. }
  10598. if(nurButton !== undefined)
  10599. {
  10600. let doneString = I18n.lookup('problems.panel.done');
  10601. let btnCaptionIsNextPlace = (nurButton.innerHTML.indexOf(I18n.lookup('venues.update_requests.panel.next_venue')) !== -1);
  10602. let btnCaptionIsDefaultUR = (nurButton.innerHTML.indexOf(I18n.lookup('update_requests.panel.next')) !== -1);
  10603. let btnCaptionIsDefaultMP = (nurButton.innerHTML.indexOf(I18n.lookup('problems.panel.next')) !== -1);
  10604. let btnCaptionIsNextIssue = (nurButton.innerHTML.indexOf(I18n.lookup('feed.issues.next')) !== -1);
  10605.  
  10606. let updateButton = false;
  10607.  
  10608. let panelClass = reportPanel.childNodes[0].childNodes[0].className;
  10609. let isURorMPPanel = (panelClass.indexOf('problem-edit') !== -1);
  10610. let isPURPanel = (panelClass.indexOf('place-update') !== -1);
  10611.  
  10612. if(isURorMPPanel === true)
  10613. {
  10614. // user has enabled UR button mod?
  10615. if(uroUtils.GetCBChecked('_cbInhibitNURButton') === true)
  10616. {
  10617. // the native UR panel button will always either be "Next update request" or "Next issue"
  10618. updateButton = ((btnCaptionIsDefaultUR) || (btnCaptionIsNextIssue));
  10619. }
  10620.  
  10621. // user has enabled MP button mod?
  10622. if(uroUtils.GetCBChecked('_cbInhibitNMPButton') === true)
  10623. {
  10624. // there's no way to determine if the edit panel has been opened for a UR or a MP, however as MPs
  10625. // don't currently appear in the feed, the native button only uses "Next map problem" as its caption
  10626. updateButton = (updateButton || btnCaptionIsDefaultMP);
  10627. }
  10628. }
  10629. else if(isPURPanel === true)
  10630. {
  10631. if(uroUtils.GetCBChecked('_cbInhibitNPURButton') === true)
  10632. {
  10633. // for a (P)PUR, only modify the button if it's showing the "Next place" or "Next issue" caption, to
  10634. // avoid messing up the "Next" button used to move to the next part of a multi-part PUR...
  10635. updateButton = ((btnCaptionIsNextPlace === true) || (btnCaptionIsNextIssue));
  10636. }
  10637. }
  10638.  
  10639. if(updateButton === true)
  10640. {
  10641. uroDBG.AddLog('inhibit Next UR/MP/PUR button');
  10642.  
  10643. // alter the button caption
  10644. nurButton.innerHTML = uroUtils.ModifyHTML(doneString);
  10645. // Add a new click handler to override the native one - this acts both to prevent the normal action of the "Next UR/MP/PUR" button in
  10646. // moving to the next UR/MP/PUR, and also allows us to warn about closing the UR panel if there's an unsent comment...
  10647. nurButton.addEventListener("click", uroInhibitNextUpdateRequestButton, false);
  10648. }
  10649. }
  10650. uroInhibitURFiltering = false;
  10651. }
  10652. }
  10653. function uroTSTCommentAddedHandler()
  10654. {
  10655. // test for the opening or closing of the UR editing dialog so we can detect when a new comment is added
  10656. let URDialogIsOpen = false;
  10657. let panelOpen = (document.getElementById('panel-container').firstChild !== null);
  10658.  
  10659. if(panelOpen)
  10660. {
  10661. URDialogIsOpen = (document.getElementById('panel-container').getElementsByClassName('conversation').length > 0);
  10662. }
  10663.  
  10664. if(URDialogIsOpen)
  10665. {
  10666. let thisSelectedURID = document.getElementsByClassName('permalink')[0].href.split('&mapUpdateRequest=');
  10667. if(thisSelectedURID.length > 1)
  10668. {
  10669. thisSelectedURID = thisSelectedURID[1].split('&')[0];
  10670. }
  10671. else
  10672. {
  10673. thisSelectedURID = null;
  10674. }
  10675.  
  10676. if((thisSelectedURID != uroSelectedURID) || ((thisSelectedURID != uroMarkers.clickedOnID) && (uroMarkers.clickedOnID != null)))
  10677. {
  10678. // if the user selects a new UR whilst the editing dialog is still open, treat it in the
  10679. // same way as if the user had selected that UR with the dialog closed
  10680. uroURDialogIsOpen = false;
  10681. uroSelectedURID = null;
  10682. }
  10683.  
  10684. if(((uroURDialogIsOpen === false) && (uroSelectedURID === null)) || (uroURReclickAttempts > 0))
  10685. {
  10686. // user is editing a new UR
  10687.  
  10688. // add our own click event handler to the Send button, so we can do stuff whenever a new comment is added
  10689. if(document.getElementsByClassName('new-comment-form').length > 0)
  10690. {
  10691. if(document.getElementsByClassName('new-comment-form')[0].getElementsByClassName('send-button').length > 0)
  10692. {
  10693. document.getElementsByClassName('new-comment-form')[0].getElementsByClassName('send-button')[0].addEventListener("click", uroAddedComment, false);
  10694.  
  10695. uroSelectedURID = thisSelectedURID;
  10696. uroDBG.AddLog('user is editing UR '+uroSelectedURID);
  10697. uroExpectedCommentCount = W.model.updateRequestSessions.objects[uroSelectedURID].attributes.comments.length;
  10698.  
  10699. if((uroHoveredURID !== null) && (uroSelectedURID !== null) && (parseInt(uroHoveredURID) !== parseInt(uroSelectedURID)))
  10700. {
  10701. if(uroURReclickAttempts === 0)
  10702. {
  10703. uroDBG.AddLog('DANGER, WILL ROBINSON! You clicked on UR ID '+uroHoveredURID+' but WME has loaded the details for UR ID '+uroSelectedURID+' instead, attempting to fix...');
  10704. }
  10705. if(++uroURReclickAttempts < 3)
  10706. {
  10707. //uroRestackMarkers();
  10708. let urMarker = uroGetMarker(uroLayers.ID.UR,uroHoveredURID);
  10709. if(urMarker !== null)
  10710. {
  10711. let urMarkerAttributes = uroGetAttributes(uroLayers.ID.UR, uroHoveredURID);
  10712. if(urMarkerAttributes !== null)
  10713. {
  10714. urMarkerAttributes.geometry.x = urMarkerAttributes.geometry.realX;
  10715. urMarkerAttributes.geometry.y = urMarkerAttributes.geometry.realY;
  10716. uroOpenURDialog(uroHoveredURID);
  10717. }
  10718. }
  10719. return;
  10720. }
  10721. else
  10722. {
  10723. uroDBG.AddLog('Woe is me, attempting to open UR ID '+uroHoveredURID+' has failed...');
  10724. uroAlertBox.Show('fa-warning', 'URO+ Warning', 'WME may have opened the details panel for a different UR to the one you selected, proceed with caution', false, "OK", "", null, null);
  10725. }
  10726. }
  10727. uroURReclickAttempts = 0;
  10728. uroFilterURs();
  10729. }
  10730. }
  10731. }
  10732. }
  10733. else if(uroURDialogIsOpen === true)
  10734. {
  10735. // dialog was open and has now been closed
  10736. uroSelectedURID = null;
  10737. uroMarkers.clickedOnID = null;
  10738. uroFilterURs();
  10739. }
  10740. uroURDialogIsOpen = URDialogIsOpen;
  10741.  
  10742. if(((uroPendingCommentDataRefresh === true) || (uroWaitingCommentDataRefresh === true)) && (uroSelectedURID !== null))
  10743. {
  10744. uroDBG.AddLog('check completion of comment data refresh for UR '+uroSelectedURID+' ('+uroPendingCommentDataRefresh+','+uroWaitingCommentDataRefresh+')');
  10745. uroGetSelectedURCommentCount();
  10746. }
  10747.  
  10748. }
  10749. function uroClosureListHandler()
  10750. {
  10751. // Handles adjustments to the closure list in the sidepanel...
  10752. //
  10753. if(uroUtils.GetCBChecked('_cbMasterEnable') === false)
  10754. {
  10755. return;
  10756. }
  10757.  
  10758. // List entry filtering
  10759. let nEntries = document.querySelectorAll('.closure-item').length;
  10760. if(nEntries > 0)
  10761. {
  10762. let filterExpired = uroUtils.GetCBChecked('_cbHideExpiredSidepanelRTCs');
  10763. let filterCurrent = uroUtils.GetCBChecked('_cbHideSidepanelRTCs');
  10764. let filterFuture = uroUtils.GetCBChecked('_cbHideFutureSidepanelRTCs');
  10765. let filterUnknown = uroUtils.GetCBChecked('_cbHideUnknownSidepanelRTCs');
  10766. for(let i = 0; i < nEntries; ++i)
  10767. {
  10768. let hide = false;
  10769. let cElm = document.querySelectorAll('.closure-item')[i];
  10770. let ciSrc = cElm.querySelector('img')?.src;
  10771. if(ciSrc != undefined)
  10772. {
  10773. hide |= ((ciSrc.indexOf('finished') != -1) && (filterExpired == true));
  10774. hide |= ((ciSrc.indexOf('active') != -1) && (filterCurrent == true));
  10775. hide |= ((ciSrc.indexOf('not-started') != -1) && (filterFuture == true));
  10776. hide |= ((ciSrc === "") && (filterUnknown == true));
  10777. }
  10778.  
  10779. if(hide == true)
  10780. {
  10781. cElm.style.display = "none";
  10782. }
  10783. else
  10784. {
  10785. cElm.style.display = "block";
  10786. }
  10787. }
  10788. }
  10789.  
  10790. // Closure cloning controls
  10791. if((document.querySelectorAll('.closures-list').length > 0) && (document.querySelector('.closures-list').getAttribute('touchedbyuro') === null))
  10792. {
  10793. let nClosures;
  10794. let cLoop;
  10795. let btnElm;
  10796.  
  10797. // Cloning doesn't work with certain locales due to the way the date strings are formatted...
  10798. if
  10799. (
  10800. (I18n.locale == "fa-IR") ||
  10801. (I18n.locale == 'ar') ||
  10802. (I18n.locale == 'zh') ||
  10803. (I18n.locale == 'ko')
  10804. )
  10805. {
  10806. // Sorry :-(
  10807. }
  10808. else
  10809. {
  10810. // for the others, are there any closures defined for all of the selected segment(s)...
  10811. if(document.getElementsByClassName('full-closures').length > 0)
  10812. {
  10813. nClosures = document.getElementsByClassName('full-closures')[0].querySelectorAll('.closure-item.is-editable').length;
  10814. if(nClosures > 0)
  10815. {
  10816. // Force a refresh of uroRTCObjs for the selected segment, as this is no longer guaranteed to have already occurred...
  10817. let selectedSegIDs = uroUtils.GetSelectedSegmentIDs();
  10818. if(selectedSegIDs.length > 0)
  10819. {
  10820. uroGetSelectedSegmentRTCs(selectedSegIDs[0]);
  10821.  
  10822. // and if so, have we already added the clone icon?
  10823. for(cLoop = 0; cLoop < nClosures; cLoop++)
  10824. {
  10825. btnElm = document.getElementsByClassName('full-closures')[0].querySelectorAll('.closure-item.is-editable')[cLoop].getElementsByClassName('closure-title')[0];
  10826. if((btnElm.innerHTML.indexOf('_uroCloneClosure-') == -1) && (uroGetRTCOrigin(uroRTCObjs[cLoop]) !== uroEnums.TRTC.UNKNOWN))
  10827. {
  10828. let newNode = document.createElement("div");
  10829. let anchorID1 = '_uroCloneClosure-1-'+cLoop;
  10830. let anchorID2 = '_uroCloneClosure-7-'+cLoop;
  10831. let newAnchor = '<a id="'+anchorID1+'" href="#">';
  10832. newAnchor += "<i style='font-size: 150%; cursor: copy' class='fa fa-copy'></i>";
  10833. newAnchor += "</a><sup>+1</sup>&nbsp;";
  10834. newAnchor += '<a id="'+anchorID2+'" href="#">';
  10835. newAnchor += "<i style='font-size: 150%; cursor: copy' class='fa fa-copy'></i>";
  10836. newAnchor += "</a><sup>+7</sup>";
  10837. newNode.innerHTML = uroUtils.ModifyHTML(newAnchor);
  10838. btnElm.prepend(newNode);
  10839. uroUtils.AddEventListener(anchorID1,"click",uroRTCClone.Clone,false);
  10840. uroUtils.AddEventListener(anchorID2,"click",uroRTCClone.Clone,false);
  10841.  
  10842. let chipElm = btnElm.querySelector("wz-image-chip");
  10843. chipElm.innerHTML = chipElm.innerHTML.split('>')[0] + '>';
  10844. }
  10845. }
  10846. }
  10847. }
  10848. }
  10849. }
  10850.  
  10851. // if there's more than one closure (full or partial) listed, also add the delete all button if not already present
  10852. nClosures = document.querySelectorAll('.closure-item.is-editable').length;
  10853. if(nClosures > 0)
  10854. {
  10855. if(document.getElementById('_btnDeleteAllClosures') === null)
  10856. {
  10857. let daDiv = document.createElement('wz-button');
  10858. daDiv.className = 'delete-all-button btn is-expanded'; //btn-primary';
  10859. daDiv.id = '_btnDeleteAllClosures';
  10860.  
  10861. let tHTML = '<i class="fa fa-trash"></i> '+I18n.lookup("closures.delete_confirm_no_reason");
  10862. if(nClosures > 1)
  10863. {
  10864. tHTML += ' ('+I18n.lookup("closures.apply_to_all")+')';
  10865. }
  10866. daDiv.innerHTML = uroUtils.ModifyHTML(tHTML);
  10867. daDiv.style.width = '100%';
  10868. daDiv.style.marginBottom = '10px';
  10869.  
  10870. let acBtn = document.getElementsByClassName('add-closure-button')[0];
  10871. if(acBtn !== undefined)
  10872. {
  10873. acBtn.parentNode.insertBefore(daDiv, acBtn.nextSibling);
  10874. uroUtils.AddEventListener('_btnDeleteAllClosures',"click", uroRTCClone.DeleteAll, false);
  10875. }
  10876. }
  10877. }
  10878.  
  10879. document.querySelector('.closures-list').setAttribute('touchedbyuro','true');
  10880. }
  10881. }
  10882. function uroMiscUITweaksHandler()
  10883. {
  10884. if(uroFilterPreamble())
  10885. {
  10886. // clickifies the ExtraInfo URL present in some MPs
  10887. {
  10888. if(document.getElementById('panel-container').getElementsByClassName('extraInfo').length > 0)
  10889. {
  10890. if(document.getElementById('panel-container').getElementsByClassName('extraInfo')[0].touchedByURO === undefined)
  10891. {
  10892. let tDesc = document.getElementById('panel-container').getElementsByClassName('extraInfo')[0].innerHTML;
  10893. tDesc = uroUtils.Clickify(tDesc, '');
  10894. document.getElementById('panel-container').getElementsByClassName('extraInfo')[0].innerHTML = uroUtils.ModifyHTML(tDesc);
  10895. document.getElementById('panel-container').getElementsByClassName('extraInfo')[0].touchedByURO = true;
  10896. }
  10897. }
  10898. }
  10899. }
  10900. }
  10901. function uroMainTick()
  10902. {
  10903. if(uroMTEMode) return;
  10904. if(uroInit.setupListeners)
  10905. {
  10906. if(uroInit.finalisingListenerSetup === false)
  10907. {
  10908. if(W.loginManager.isLoggedIn())
  10909. {
  10910. if(document.getElementsByClassName('topbar').length === 0) return;
  10911. uroInit.FinalizeListenerSetup();
  10912. document.getElementsByClassName('topbar')[0].appendChild(uroAMList);
  10913. }
  10914. }
  10915. }
  10916. else
  10917. {
  10918. if(uroUtils.GetCBChecked('_cbMasterEnable') === true)
  10919. {
  10920. // do one maintick handler call in each 10ms cycle to minimise the time stuck within the maintick handler without
  10921. // unduly affecting the overall response time for each individual handler
  10922.  
  10923. if(uroMainTickStage === 0) uroTSTPopupHandler();
  10924. if(uroMainTickStage == 1) uroTSTNextBtnHandler();
  10925. if(uroMainTickStage == 2) uroTSTCommentAddedHandler();
  10926. if(uroMainTickStage == 4) uroMiscUITweaksHandler();
  10927.  
  10928. if(++uroMainTickStage == 6) uroMainTickStage = 0;
  10929. }
  10930. }
  10931. }
  10932. function uroNewLookCheckDetailsRequest()
  10933. {
  10934. let thisurl = document.location.href;
  10935. let doRetry = true;
  10936. let urID;
  10937. let endmarkerpos = thisurl.indexOf('&endshow');
  10938. let showmarkerpos = thisurl.indexOf('&showturn=');
  10939.  
  10940. if((endmarkerpos != -1) && (showmarkerpos != -1))
  10941. {
  10942. showmarkerpos += 10;
  10943. uroDBG.AddLog('showturn tab opened');
  10944. urID = thisurl.substr(showmarkerpos,endmarkerpos-showmarkerpos);
  10945. uroDBG.AddLog(' turn problem ID = '+urID);
  10946.  
  10947. try
  10948. {
  10949. uroGetMarker(uroLayers.ID.MP,urID).element.click();
  10950. doRetry = false;
  10951. }
  10952. catch(err)
  10953. {
  10954. uroDBG.AddLog('problems not fully loaded, retrying...');
  10955. }
  10956.  
  10957. if(doRetry) window.setTimeout(uroNewLookCheckDetailsRequest,500);
  10958. }
  10959. else
  10960. {
  10961. showmarkerpos = thisurl.indexOf('&showpur=');
  10962. if((endmarkerpos != -1) && (showmarkerpos != -1))
  10963. {
  10964. showmarkerpos += 9;
  10965. uroDBG.AddLog('showPUR tab opened');
  10966. urID = thisurl.substr(showmarkerpos,endmarkerpos-showmarkerpos);
  10967. uroDBG.AddLog(' PUR ID = '+urID);
  10968.  
  10969. try
  10970. {
  10971. uroGetMarker(uroLayers.ID.PUR, urID).element.click();
  10972. doRetry = false;
  10973. }
  10974. catch(err)
  10975. {
  10976. uroDBG.AddLog('PURs not fully loaded, retrying...');
  10977. }
  10978.  
  10979. if(doRetry) window.setTimeout(uroNewLookCheckDetailsRequest,500);
  10980. }
  10981.  
  10982. else
  10983. {
  10984. showmarkerpos = thisurl.indexOf('&showppur=');
  10985. if((endmarkerpos != -1) && (showmarkerpos != -1))
  10986. {
  10987. showmarkerpos += 10;
  10988. uroDBG.AddLog('showPPUR tab opened');
  10989. urID = thisurl.substr(showmarkerpos,endmarkerpos-showmarkerpos);
  10990. uroDBG.AddLog(' PPUR ID = '+urID);
  10991.  
  10992. try
  10993. {
  10994. uroGetMarker(uroLayers.ID.PPUR, urID).element.click();
  10995. doRetry = false;
  10996. }
  10997. catch(err)
  10998. {
  10999. uroDBG.AddLog('PPURs not fully loaded, retrying...');
  11000. }
  11001.  
  11002. if(doRetry) window.setTimeout(uroNewLookCheckDetailsRequest,500);
  11003. }
  11004. }
  11005. }
  11006.  
  11007. }
  11008. function uroUpdateVenueEditorLists()
  11009. {
  11010. if(Object.keys(W.model.venues.objects).length === 0) return;
  11011.  
  11012. // build the list of all userIDs contained in the currently loaded venue objects
  11013. let selectedIdx = null;
  11014. let listedIDs = [];
  11015. let idx;
  11016. for(idx in W.model.venues.objects)
  11017. {
  11018. if(W.model.venues.objects.hasOwnProperty(idx))
  11019. {
  11020. let obj = W.model.venues.objects[idx].attributes;
  11021. let cbID = obj.createdBy;
  11022. let ubID = obj.updatedBy;
  11023.  
  11024. if((cbID !== null) && (listedIDs.indexOf(cbID) == -1))
  11025. {
  11026. listedIDs.push(cbID);
  11027. }
  11028. if((ubID !== null) && (ubID !== cbID) && (listedIDs.indexOf(ubID) == -1))
  11029. {
  11030. listedIDs.push(ubID);
  11031. }
  11032. }
  11033. }
  11034.  
  11035. // check for any previously selected userIDs in the two selector lists, then clear both lists
  11036. // and repopulate using the newly gathered ID collection from above, and finally reselect the
  11037. // previously selected user if they're still present in the new list...
  11038. let selector;
  11039. let selectedUser;
  11040. let users = W.model.users.getByIds(listedIDs);
  11041. let selectorEntry;
  11042.  
  11043. for(let i=0; i<2; i++)
  11044. {
  11045. if(i === 0) selector = document.getElementById('_selectPlacesUserID');
  11046. else selector = document.getElementById('_selectHidePlacesUserID');
  11047.  
  11048. selectedUser = null;
  11049. if(selector.selectedOptions[0] != null)
  11050. {
  11051. selectedUser = parseInt(selector.selectedOptions[0].value);
  11052. }
  11053. while(selector.options.length > 0)
  11054. {
  11055. selector.options.remove(0);
  11056. }
  11057. selector.options.add(new Option('<select a user>', null));
  11058. if(listedIDs.length > 0)
  11059. {
  11060. selectorEntry = '';
  11061. for(idx=0; idx<users.length; idx++)
  11062. {
  11063. if(users[idx].attributes.userName === undefined)
  11064. {
  11065. selectorEntry = users[idx].attributes.id;
  11066. }
  11067. else
  11068. {
  11069. selectorEntry = users[idx].attributes.userName;
  11070. }
  11071. selector.options.add(new Option(selectorEntry, users[idx].id));
  11072. if(users[idx].attributes.id == selectedUser)
  11073. {
  11074. selectedIdx = idx+1;
  11075. }
  11076. }
  11077. }
  11078.  
  11079. if(selectedIdx !== null)
  11080. {
  11081. selector.selectedIndex = selectedIdx;
  11082. }
  11083. }
  11084. }
  11085. function uroPlacesEditorSelected()
  11086. {
  11087. let selector = document.getElementById('_selectPlacesUserID');
  11088. if(selector.selectedIndex > 0)
  11089. {
  11090. document.getElementById('_textPlacesEditor').value = document.getElementById('_selectPlacesUserID').selectedOptions[0].innerHTML;
  11091. }
  11092. }
  11093. function uroHidePlacesEditorSelected()
  11094. {
  11095. let selector = document.getElementById('_selectHidePlacesUserID');
  11096. if(selector.selectedIndex > 0)
  11097. {
  11098. document.getElementById('_textHidePlacesEditor').value = document.getElementById('_selectHidePlacesUserID').selectedOptions[0].innerHTML;
  11099. }
  11100. }
  11101. function uroCamEditorSelected()
  11102. {
  11103. let selector = document.getElementById('_selectCameraUserID');
  11104. if(selector.selectedIndex > 0)
  11105. {
  11106. document.getElementById('_textCameraEditor').value = document.getElementById('_selectCameraUserID').selectedOptions[0].innerHTML;
  11107. }
  11108. }
  11109. function uroSetStyles(obj)
  11110. {
  11111. obj.style.fontSize = '12px';
  11112. obj.style.lineHeight = '100%';
  11113. obj.style.flex = '1';
  11114. obj.style.overflowY = 'auto';
  11115. }
  11116. function uroSetSectionTabStyles()
  11117. {
  11118. for(let i =0; i < uroTabs.CtrlTabs.length; ++i)
  11119. {
  11120. uroSetStyles(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY]);
  11121. }
  11122. }
  11123. function uroPlacesGroupCEHandler(groupidx)
  11124. {
  11125. if(uroPlacesGroupsCollapsed[groupidx] === false)
  11126. {
  11127. document.getElementById('_uroPlacesGroup-'+groupidx).style.display = "block";
  11128. document.getElementById('_uroPlacesGroupState-'+groupidx).className = "fa fa-minus-square-o";
  11129. }
  11130. else
  11131. {
  11132. document.getElementById('_uroPlacesGroup-'+groupidx).style.display = "none";
  11133. document.getElementById('_uroPlacesGroupState-'+groupidx).className = "fa fa-plus-square-o";
  11134. }
  11135. }
  11136. function uroPlacesGroupCollapseExpand()
  11137. {
  11138. let groupidx = this.id.substr(21);
  11139. if(uroPlacesGroupsCollapsed[groupidx] === true) uroPlacesGroupsCollapsed[groupidx] = false;
  11140. else uroPlacesGroupsCollapsed[groupidx] = true;
  11141. uroPlacesGroupCEHandler(groupidx);
  11142. return false;
  11143. }
  11144. function uroGetMarkerIDs(markerType)
  11145. {
  11146. let idList = [];
  11147.  
  11148. if(uroLayers.layers[markerType].mf !== null)
  11149. {
  11150. // Refresh .mf, as this is no longer guaranteed to have been set up as a reference to
  11151. // whatever's in the current W.map.layers object...
  11152. uroLayers.layers[markerType].mf = uroLayers.GetMarkersOrFeatures(markerType);
  11153. for(let i = 0; i < uroLayers.layers[markerType].mf.length; ++i)
  11154. {
  11155. let dID = uroLayers.layers[markerType].mf[i]?.element?.attributes['data-id']?.value;
  11156. if(dID === undefined)
  11157. {
  11158. dID = uroLayers.layers[markerType].mf[i]?.attributes?.wazeFeature?.id;
  11159. }
  11160. if(dID !== undefined)
  11161. {
  11162. idList.push(dID);
  11163. }
  11164. }
  11165. }
  11166. return idList;
  11167. }
  11168. function uroGetAttributes(markerType, markerID)
  11169. {
  11170. if(markerType == uroLayers.ID.UR) return W.model.mapUpdateRequests.objects[markerID].attributes;
  11171. if(markerType == uroLayers.ID.MP) return W.model.mapProblems.objects[markerID].attributes;
  11172. return null;
  11173. }
  11174. function uroGetMarker(markerType, markerID)
  11175. {
  11176. if(typeof(markerID) === 'number')
  11177. {
  11178. markerID = markerID.toString();
  11179. }
  11180.  
  11181. let retval = null;
  11182.  
  11183. if(markerID !== null)
  11184. {
  11185. let mObj = null;
  11186. if(markerType === uroLayers.ID.UR)
  11187. {
  11188. mObj = W.model.mapUpdateRequests.getObjectById(markerID);
  11189. }
  11190. else if(markerType === uroLayers.ID.MP)
  11191. {
  11192. mObj = W.model.mapProblems.getObjectById(markerID);
  11193. }
  11194. else if(markerType === uroLayers.ID.RTC)
  11195. {
  11196. mObj = W.model.roadClosures.getObjectById(markerID);
  11197. }
  11198.  
  11199. if(mObj !== null)
  11200. {
  11201. retval = W.userscripts.getMapElementByDataModel(mObj);
  11202. }
  11203. else
  11204. {
  11205. for(let i = 0; i < uroLayers.layers[markerType].mf.length; ++i)
  11206. {
  11207. if(uroLayers.layers[markerType].mf[i]?.attributes?.wazeFeature?.id === markerID)
  11208. {
  11209. let geoID = uroLayers.layers[markerType].mf[i].geometry.id;
  11210. retval = document.getElementById(geoID);
  11211. break;
  11212. }
  11213. }
  11214. }
  11215. }
  11216. return retval;
  11217. }
  11218.  
  11219. uroInit.Initialise();