WME E95

Setup road properties in one click

目前为 2023-01-12 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name WME E95
  3. // @version 0.7.0
  4. // @description Setup road properties in one click
  5. // @license MIT License
  6. // @author Anton Shevchuk
  7. // @namespace https://greasyfork.org/users/227648-anton-shevchuk
  8. // @supportURL https://github.com/AntonShevchuk/wme-e95/issues
  9. // @match https://*.waze.com/editor*
  10. // @match https://*.waze.com/*/editor*
  11. // @exclude https://*.waze.com/user/editor*
  12. // @icon data:image/png;base64,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
  13. // @grant none
  14. // @require https://greasyfork.org/scripts/389765-common-utils/code/CommonUtils.js?version=1090053
  15. // @require https://greasyfork.org/scripts/450160-wme-bootstrap/code/WME-Bootstrap.js?version=1135567
  16. // @require https://greasyfork.org/scripts/452563-wme/code/WME.js?version=1101598
  17. // @require https://greasyfork.org/scripts/450221-wme-base/code/WME-Base.js?version=1137043
  18. // @require https://greasyfork.org/scripts/450320-wme-ui/code/WME-UI.js?version=1137289
  19. // ==/UserScript==
  20.  
  21. /* jshint esversion: 8 */
  22. /* global require */
  23. /* global $, jQuery */
  24. /* global W */
  25. /* global I18n */
  26. /* global WME, WMEBase, WMEUI, WMEUIHelper, WMEUIShortcut, WMEUIHelperControlButton */
  27. /* global Container, Settings, SimpleCache, Tools */
  28.  
  29. (function () {
  30. 'use strict'
  31.  
  32. // Script name, uses as unique index
  33. const NAME = 'E95'
  34.  
  35. // Translations
  36. const TRANSLATION = {
  37. 'en': {
  38. title: 'Quick Properties',
  39. },
  40. 'uk': {
  41. title: 'Швидкі налаштування',
  42. },
  43. 'ru': {
  44. title: 'Быстрые настройки'
  45. }
  46. }
  47.  
  48. const STYLE = 'button.waze-btn.E95 { margin: 0 4px 4px 0; padding: 2px; width: 42px; } ' +
  49. 'button.waze-btn.E95:hover { box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1), inset 0 0 100px 100px rgba(255, 255, 255, 0.3); } ' +
  50. 'button.waze-btn.E95-E { margin-right: 42px; }' +
  51. 'button.waze-btn.E95-J { margin-right: 42px; }'
  52.  
  53. WMEUI.addTranslation(NAME, TRANSLATION)
  54. WMEUI.addStyle(STYLE)
  55.  
  56. // Road Types
  57. // I18n.translations.uk.segment.road_types
  58. // I18n.translations.en.segment.road_types
  59. const TYPES = {
  60. street: 1,
  61. primary: 2,
  62. freeway: 3,
  63. ramp: 4,
  64. trail: 5,
  65. major: 6,
  66. minor: 7,
  67. offroad: 8,
  68. walkway: 9,
  69. boardwalk: 10,
  70. ferry: 15,
  71. stairway: 16,
  72. private: 17,
  73. railroad: 18,
  74. runway: 19,
  75. parking: 20,
  76. narrow: 22
  77. }
  78. // Road colors by type
  79. const COLORS = {
  80. '1': '#ffffeb',
  81. '2': '#f0ea58',
  82. // ...
  83. '8': '#867342',
  84. // ...
  85. '17': '#beba6c',
  86. // ...
  87. '20': '#ababab'
  88. }
  89. // Road Flags
  90. // for setup flags use binary operators
  91. // e.g. flags.tunnel | flags.headlights
  92. const FLAGS = {
  93. tunnel: 0b00000001,
  94. // ???
  95. // a : 0b00000010,
  96. // b : 0b00000100,
  97. // c : 0b00001000,
  98. unpaved: 0b00010000,
  99. headlights: 0b00100000,
  100. }
  101. // Buttons:
  102. // title - for buttons
  103. // shortcut - keys for shortcuts, by default is Alt + (1..9)
  104. // options:
  105. // - detectCity - try to detect the city name by closures segments
  106. // - clearCity - clear the city name
  107. // - clearStreet - clear the street name
  108. // attributes - native settings for model object
  109. // TODO:
  110. // – check permissions for user level lower than 2
  111. const BUTTONS = {
  112. A: {
  113. title: 'Pr 5',
  114. shortcut: 'A+1',
  115. options: {
  116. detectCity: true,
  117. },
  118. attributes: {
  119. flags: 0,
  120. fwdMaxSpeed: 5,
  121. revMaxSpeed: 5,
  122. fwdMaxSpeedUnverified: false,
  123. revMaxSpeedUnverified: false,
  124. roadType: TYPES.private,
  125. lockRank: 0,
  126. }
  127. },
  128. B: {
  129. title: 'Pr20',
  130. shortcut: 'A+2',
  131. options: {
  132. detectCity: true,
  133. },
  134. attributes: {
  135. flags: 0,
  136. fwdMaxSpeed: 20,
  137. revMaxSpeed: 20,
  138. fwdMaxSpeedUnverified: false,
  139. revMaxSpeedUnverified: false,
  140. roadType: TYPES.private,
  141. lockRank: 0,
  142. }
  143. },
  144. C: {
  145. title: 'Pr50',
  146. shortcut: 'A+3',
  147. options: {
  148. detectCity: true,
  149. },
  150. attributes: {
  151. flags: 0,
  152. fwdMaxSpeed: 50,
  153. revMaxSpeed: 50,
  154. fwdMaxSpeedUnverified: false,
  155. revMaxSpeedUnverified: false,
  156. roadType: TYPES.private,
  157. lockRank: 0,
  158. }
  159. },
  160. D: {
  161. title: 'St50',
  162. shortcut: 'A+4',
  163. options: {
  164. detectCity: true,
  165. },
  166. attributes: {
  167. flags: 0,
  168. fwdMaxSpeed: 50,
  169. revMaxSpeed: 50,
  170. fwdMaxSpeedUnverified: false,
  171. revMaxSpeedUnverified: false,
  172. roadType: TYPES.street,
  173. lockRank: 0,
  174. }
  175. },
  176. E: {
  177. title: 'PS50',
  178. shortcut: 'A+5',
  179. options: {
  180. detectCity: true,
  181. },
  182. attributes: {
  183. flags: 0,
  184. fwdMaxSpeed: 50,
  185. revMaxSpeed: 50,
  186. fwdMaxSpeedUnverified: false,
  187. revMaxSpeedUnverified: false,
  188. roadType: TYPES.primary,
  189. lockRank: 1,
  190. }
  191. },
  192. F: {
  193. title: 'PLR',
  194. shortcut: 'A+6',
  195. options: {
  196. detectCity: true,
  197. },
  198. attributes: {
  199. flags: 0,
  200. fwdMaxSpeed: 5,
  201. revMaxSpeed: 5,
  202. fwdMaxSpeedUnverified: false,
  203. revMaxSpeedUnverified: false,
  204. roadType: TYPES.parking,
  205. lockRank: 0,
  206. }
  207. },
  208. G: {
  209. title: 'OR',
  210. shortcut: 'A+7',
  211. options: {
  212. clearCity: true,
  213. clearStreet: false,
  214. },
  215. attributes: {
  216. flags: 0,
  217. fwdMaxSpeed: 90,
  218. revMaxSpeed: 90,
  219. fwdMaxSpeedUnverified: false,
  220. revMaxSpeedUnverified: false,
  221. roadType: TYPES.offroad,
  222. lockRank: 0,
  223. }
  224. },
  225. H: {
  226. title: 'Pr90',
  227. shortcut: 'A+8',
  228. options: {
  229. clearCity: true,
  230. },
  231. attributes: {
  232. flags: 0,
  233. fwdMaxSpeed: 90,
  234. revMaxSpeed: 90,
  235. fwdMaxSpeedUnverified: false,
  236. revMaxSpeedUnverified: false,
  237. roadType: TYPES.private,
  238. lockRank: 0,
  239. }
  240. },
  241. I: {
  242. title: 'St90',
  243. shortcut: 'A+9',
  244. options: {
  245. clearCity: true,
  246. },
  247. attributes: {
  248. flags: 0,
  249. fwdMaxSpeed: 90,
  250. revMaxSpeed: 90,
  251. fwdMaxSpeedUnverified: false,
  252. revMaxSpeedUnverified: false,
  253. roadType: TYPES.street,
  254. lockRank: 0,
  255. }
  256. },
  257. J: {
  258. title: 'PS90',
  259. shortcut: 'A+0',
  260. options: {
  261. clearCity: true,
  262. },
  263. attributes: {
  264. flags: 0,
  265. fwdMaxSpeed: 90,
  266. revMaxSpeed: 90,
  267. fwdMaxSpeedUnverified: false,
  268. revMaxSpeedUnverified: false,
  269. roadType: TYPES.primary,
  270. lockRank: 1,
  271. }
  272. }
  273. }
  274.  
  275. // country specified buttons config
  276. const CONFIGS = {
  277. // Ukraine
  278. 232: {
  279. G: {
  280. attributes: {
  281. flags: FLAGS.headlights
  282. }
  283. },
  284. H: {
  285. attributes: {
  286. flags: FLAGS.headlights
  287. }
  288. },
  289. I: {
  290. attributes: {
  291. flags: FLAGS.headlights
  292. }
  293. },
  294. J: {
  295. attributes: {
  296. flags: FLAGS.headlights
  297. }
  298. },
  299. }
  300. }
  301.  
  302. // Require Waze API
  303. let WazeActionUpdateObject
  304. let WazeActionUpdateFeatureAddress
  305.  
  306. class E95 extends WMEBase {
  307. constructor (name, buttons, configs) {
  308. super(name)
  309.  
  310. this.panel = null
  311. this.buttons = buttons
  312. this.configs = configs
  313. }
  314.  
  315. getPanel () {
  316. if (this.panel) {
  317. return this.panel
  318. }
  319.  
  320. // Build panel
  321. // Container for buttons
  322. let controls = document.createElement('div')
  323. controls.className = 'controls'
  324. // Create buttons
  325. for (let btn in this.buttons) {
  326. let config = this.getButtonConfig(btn)
  327. let title = config.title
  328. let color = COLORS[config.attributes.roadType]
  329. let description = config.title + ' - ' +
  330. I18n.t('segment.road_types')[config.attributes.roadType] + '; ' +
  331. I18n.t('edit.segment.fields.speed_limit') + ' ' +
  332. I18n.t('measurements.speed.km', { speed: config.attributes.fwdMaxSpeed })
  333.  
  334. let UIButton = new WMEUIHelperControlButton(
  335. NAME,
  336. btn,
  337. title,
  338. description,
  339. () => this.setupCallback(config),
  340. config.shortcut
  341. )
  342. let button = UIButton.html()
  343. button.dataset[NAME] = btn
  344. button.style.backgroundColor = color
  345. controls.appendChild(button)
  346. }
  347. let label = document.createElement('label')
  348. label.className = 'control-label'
  349. label.innerText = I18n.t(NAME).title
  350.  
  351. this.panel = document.createElement('div')
  352. this.panel.className = 'form-group ' + NAME
  353. this.panel.appendChild(label)
  354. this.panel.appendChild(controls)
  355.  
  356. return this.panel
  357. }
  358.  
  359. // Get Button settings
  360. getButtonConfig (index) {
  361. // Load settings for current country by call method W.model.getTopCountry().getID()
  362. // Then mixed it with default settings by Tools.mergeDeep() method
  363. return Tools.mergeDeep(this.buttons[index], this.configs[W.model.getTopCountry().getID()][index])
  364. }
  365.  
  366. // Handler for Road buttons
  367. setupCallback (button) {
  368. // Get all selected segments
  369. let segments = WME.getSelectedSegments()
  370.  
  371. // Try to detect city, if needed
  372. if (button.options.detectCity) {
  373. let cityName = null
  374. for (let i = 0, total = segments.length; i < total; i++) {
  375. cityName = this.detectCity(segments[i])
  376. if (cityName) {
  377. button.options.cityName = cityName
  378. break
  379. }
  380. }
  381. }
  382. for (let i = 0, total = segments.length; i < total; i++) {
  383. this.updateSegment(segments[i], button.options, button.attributes)
  384. }
  385. }
  386.  
  387. /**
  388. * Update segment attributes
  389. * @param {Object} segment
  390. * @param {Object} options
  391. * @param {Object} attributes
  392. */
  393. updateSegment (segment, options, attributes = {}) {
  394. // current segment address
  395. let addr = segment.getAddress().attributes
  396. // fill address information
  397. let address = {
  398. countryID: addr.country ? addr.country.id : W.model.getTopCountry().getID(),
  399. stateID: addr.state ? addr.state.id : W.model.getTopState().getID(),
  400. cityName: addr.city ? addr.city.attributes.name : null,
  401. streetName: addr.street ? addr.street.name : null,
  402. }
  403. // options: detect city
  404. if (!address.cityName && options.detectCity && options.cityName) {
  405. this.log('detected city name "' + options.cityName + '"')
  406. address.cityName = options.cityName
  407. }
  408. // options: clear city
  409. if (options.clearCity) {
  410. this.log('clear city name')
  411. address.cityName = null
  412. }
  413. // options: clear street
  414. if (options.clearStreet) {
  415. this.log('clear street name')
  416. address.streetName = null
  417. }
  418. // set city flag
  419. address.emptyCity = (address.cityName === null)
  420. // set street flag
  421. address.emptyStreet = (address.streetName === null) || (address.streetName === '')
  422. // update segment's address
  423. W.model.actionManager.add(
  424. new WazeActionUpdateFeatureAddress(
  425. segment,
  426. address,
  427. {
  428. streetIDField: 'primaryStreetID'
  429. }
  430. )
  431. )
  432. // keep the current lock level if it is higher than in the config's attributes
  433. if (segment.attributes.lockRank > attributes.lockRank) {
  434. attributes.lockRank = segment.attributes.lockRank
  435. }
  436. // need more logs
  437. this.log('set road type to ' + I18n.t('segment.road_types')[attributes.roadType])
  438. // update segment's properties
  439. W.model.actionManager.add(
  440. new WazeActionUpdateObject(
  441. segment,
  442. attributes
  443. )
  444. )
  445. }
  446.  
  447. /**
  448. * Detect city name by connected segments
  449. * @param {Object} segment
  450. * @return {String|null}
  451. */
  452. detectCity (segment) {
  453. // check cityName of the segment
  454. if (segment.getAddress().getCity() && !segment.getAddress().getCity().isEmpty()) {
  455. return segment.getAddress().getCity().getName()
  456. }
  457.  
  458. // TODO: replace follow magic with methods getConnectedSegments() and getConnectedSegmentsByDirection()
  459. // W.selectionManager.getSelectedFeatures()[0].model.getConnectedSegments().map(x => x.attributes.id)
  460. // W.selectionManager.getSelectedFeatures()[0].model.getConnectedSegmentsByDirection().map(x => x.attributes.id)
  461. // when it will work
  462. // last check - 2022.12.29
  463.  
  464. // connected segments
  465. let connected = []
  466. connected = connected.concat(segment.getFromNode().getSegmentIds()) // segments from point A
  467. connected = connected.concat(segment.getToNode().getSegmentIds()) // segments from point B
  468. connected = connected.filter(id => id !== segment.getID()) // filter himself
  469.  
  470. // cities of the connected segments
  471. let cities = connected.map(id => W.model.segments.getObjectById(id).getAddress().getCity())
  472. cities = cities.filter(city => city) // filter segments w/out city
  473. cities = cities.map(city => city.getName()) // extract cities name
  474. cities = cities.filter(city => city) // filter empty city name
  475.  
  476. if (cities.length) {
  477. return cities.shift()
  478. }
  479. return null
  480. }
  481.  
  482. /**
  483. * Handler for `segment.wme` event
  484. * Create UI controls every time when updated DOM of sidebar
  485. * Uses native JS function for better performance
  486. *
  487. * @param {jQuery.Event} event
  488. * @param {HTMLElement} element
  489. * @param {W.model} model
  490. * @return {void}
  491. */
  492. onSegment (event, element, model) {
  493. // Skip for walking trails
  494. if (model.isWalkingRoadType()) {
  495. return
  496. }
  497. // Panel should already create
  498. element.prepend(this.getPanel())
  499. }
  500.  
  501. /**
  502. * Handler for `segments.wme` event
  503. * Create UI controls every time when updated DOM of sidebar
  504. * Uses native JS function for better performance
  505. *
  506. * @param {jQuery.Event} event
  507. * @param {HTMLElement} element
  508. * @param {Array} models
  509. * @return {void}
  510. */
  511. onSegments (event, element, models) {
  512. // Skip for walking trails
  513. if (models.filter((model) => !model.isWalkingRoadType()).length === 0) {
  514. return
  515. }
  516. // Panel should already create
  517. element.prepend(this.getPanel())
  518. }
  519. }
  520.  
  521. $(document).on('bootstrap.wme', () => {
  522. // Require scripts
  523. WazeActionUpdateObject = require('Waze/Action/UpdateObject')
  524. WazeActionUpdateFeatureAddress = require('Waze/Action/UpdateFeatureAddress')
  525.  
  526. new E95(NAME, BUTTONS, CONFIGS)
  527.  
  528. WMEUIShortcut.setGroupTitle(NAME, I18n.t(NAME).title)
  529. })
  530. })()