WME Bootstrap

Bootstrap library for custom Waze Map Editor scripts

当前为 2023-07-12 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/450160/1218867/WME%20Bootstrap.js

  1. // ==UserScript==
  2. // @name WME Bootstrap
  3. // @version 0.1.5
  4. // @description Bootstrap library for custom Waze Map Editor scripts
  5. // @license MIT License
  6. // @author Anton Shevchuk
  7. // @namespace https://greasyfork.org/users/227648-anton-shevchuk
  8. // @supportURL https://github.com/AntonShevchuk/wme-bootstrap/issues
  9. // @match https://*.waze.com/editor*
  10. // @match https://*.waze.com/*/editor*
  11. // @exclude https://*.waze.com/user/editor*
  12. // @icon https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://anton.shevchuk.name&size=64
  13. // @grant none
  14. // ==/UserScript==
  15.  
  16. /* jshint esversion: 8 */
  17. /* global jQuery, W */
  18.  
  19. (function () {
  20. 'use strict'
  21.  
  22. const SELECTORS = {
  23. node: 'div.connections-edit',
  24. segment: '#segment-edit-general',
  25. venue: '#venue-edit-general',
  26. merge: '#mergeVenuesCollection'
  27. }
  28.  
  29. class Bootstrap {
  30. /**
  31. * Bootstrap it once!
  32. */
  33. constructor () {
  34. const sandbox = typeof unsafeWindow !== 'undefined'
  35. const pageWindow = sandbox ? unsafeWindow : window
  36.  
  37. if (!pageWindow.WMEBootstrap) {
  38. pageWindow.WMEBootstrap = true
  39. document.addEventListener(
  40. 'wme-ready',
  41. () => this.init(),
  42. { once: true },
  43. );
  44. }
  45. }
  46.  
  47. /**
  48. * Initial events and handlers
  49. */
  50. init () {
  51. try {
  52. // fire `bootstrap.wme` event
  53. jQuery(document).trigger('bootstrap.wme')
  54. // setup additional handlers
  55. this.setup()
  56. // listen all events
  57. jQuery(document)
  58. .on('segment.wme', () => this.log('🛣️ segment.wme'))
  59. .on('segments.wme', () => this.log('🛣️️ segments.wme'))
  60. .on('node.wme', () => this.log('⭐️ node.wme'))
  61. .on('nodes.wme', () => this.log('⭐️ nodes.wme'))
  62. .on('venue.wme', () => this.log('📍️ venue.wme'))
  63. .on('venues.wme', () => this.log('🏬️ venues.wme'))
  64. .on('point.wme', () => this.log('️🏠 point.wme'))
  65. .on('place.wme', () => this.log('🏢️️ place.wme'))
  66. .on('residential.wme', () => this.log('🪧 residential.wme'))
  67. } catch (e) {
  68. console.error(e)
  69. }
  70. }
  71.  
  72. /**
  73. * Setup additional handler for `selectionchanged` event
  74. */
  75. setup () {
  76. // register handler for selection
  77. W.selectionManager.events.register(
  78. 'selectionchanged',
  79. null,
  80. () => this.handler(W.selectionManager.getSelectedDataModelObjects())
  81. )
  82. // fire handler for current selection
  83. this.handler(W.selectionManager.getSelectedDataModelObjects())
  84. }
  85.  
  86. /**
  87. * Proxy-handler
  88. * @param {Array} models
  89. */
  90. handler (models) {
  91. if (models.length === 0) {
  92. jQuery(document).trigger('none.wme')
  93. return
  94. }
  95.  
  96. let isSingle = (models.length === 1)
  97. let model = models[0]
  98.  
  99. switch (true) {
  100. case (model.type === 'node' && isSingle):
  101. this.trigger('node.wme', SELECTORS.node, model)
  102. break
  103. case (model.type === 'node'):
  104. this.trigger('nodes.wme', SELECTORS.node, models)
  105. break
  106. case (model.type === 'segment' && isSingle):
  107. this.trigger('segment.wme', SELECTORS.segment, model)
  108. break
  109. case (model.type === 'segment'):
  110. this
  111. .waitSegmentsCounter(models.length)
  112. .then(element => jQuery(document).trigger('segments.wme', [element, models]))
  113. break
  114. case (model.type === 'venue' && isSingle):
  115. this.trigger('venue.wme', SELECTORS.venue, model)
  116. if (model.isResidential()) {
  117. this.trigger('residential.wme', SELECTORS.venue, model)
  118. } else if (model.isPoint()) {
  119. this.trigger('point.wme', SELECTORS.venue, model)
  120. } else {
  121. this.trigger('place.wme', SELECTORS.venue, model)
  122. }
  123. break
  124. case (model.type === 'venue'):
  125. this.trigger('venues.wme', SELECTORS.merge, models)
  126. break
  127. }
  128. }
  129.  
  130. /**
  131. * Fire new event with context
  132. * It can be #node-edit-general
  133. * or #segment-edit-general
  134. * or #venue-edit-general
  135. * or #mergeVenuesCollection
  136. * @param {String} event
  137. * @param {String} selector
  138. * @param {Object|Array} models
  139. */
  140. trigger (event, selector, models) {
  141. this
  142. .waitElementBySelector(selector)
  143. .then(element => jQuery(document)
  144. .trigger(event, [element, models]))
  145. }
  146.  
  147. /**
  148. * Wait for DOM Element
  149. * @param {string} selector
  150. * @return {Promise<HTMLElement>}
  151. */
  152. waitElementBySelector (selector) {
  153. return new Promise(resolve => {
  154. if (document.querySelector(selector)) {
  155. return resolve(document.querySelector(selector))
  156. }
  157.  
  158. const observer = new MutationObserver(() => {
  159. if (document.querySelector(selector)) {
  160. resolve(document.querySelector(selector))
  161. observer.disconnect()
  162. }
  163. })
  164.  
  165. observer.observe(document.getElementById('edit-panel'), {
  166. childList: true,
  167. subtree: true
  168. })
  169. })
  170. }
  171.  
  172. /**
  173. * Wait for DOM Element
  174. * @param counter
  175. * @return {Promise<HTMLElement>}
  176. */
  177. waitSegmentsCounter (counter) {
  178. let counterSelector = '#edit-panel div.segment.sidebar-column > :first-child'
  179. return new Promise(resolve => {
  180. if (
  181. document.querySelector(counterSelector)?.headline?.startsWith(counter) // beta
  182. || document.querySelector(counterSelector)?.innerText.startsWith(counter) // wme
  183. ) {
  184. return resolve(document.querySelector(SELECTORS.segment))
  185. }
  186.  
  187. const observer = new MutationObserver(() => {
  188. if (
  189. document.querySelector(counterSelector)?.headline?.startsWith(counter) // beta
  190. || document.querySelector(counterSelector)?.innerText.startsWith(counter) // wme
  191. ) {
  192. resolve(document.querySelector(SELECTORS.segment))
  193. observer.disconnect()
  194. }
  195. })
  196.  
  197. observer.observe(document.getElementById('edit-panel'), {
  198. childList: true,
  199. subtree: true
  200. })
  201. })
  202. }
  203.  
  204. /**
  205. * Just logger
  206. * @param {String} message
  207. */
  208. log (message) {
  209. console.log(
  210. '%cBootstrap:%c ' + message,
  211. 'color: #0DAD8D; font-weight: bold', 'color: dimgray; font-weight: normal'
  212. )
  213. }
  214. }
  215.  
  216. new Bootstrap()
  217.  
  218. })()