WME UI

UI Library for Waze Map Editor Greasy Fork scripts

当前为 2022-08-28 提交的版本,查看 最新版本

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

  1. // ==UserScript==
  2. // @name WME UI
  3. // @namespace https://greasyfork.org/users/227648-anton-shevchuk
  4. // @version 0.0.1
  5. // @description UI Library for Waze Map Editor Greasy Fork scripts
  6. // @license MIT License
  7. // @match https://www.waze.com/editor*
  8. // @match https://www.waze.com/*/editor*
  9. // @match https://beta.waze.com/editor*
  10. // @match https://beta.waze.com/*/editor*
  11. // @exclude https://www.waze.com/user/editor*
  12. // @exclude https://beta.waze.com/user/editor*
  13. // @icon https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://anton.shevchuk.name&size=64
  14. // @grant none
  15. // ==/UserScript==
  16.  
  17. /* jshint esversion: 8 */
  18.  
  19. /* global jQuery, W */
  20.  
  21. class WMEUI {
  22. /**
  23. * Normalize title or UID
  24. * @param string
  25. * @returns {string}
  26. */
  27. static normalize (string) {
  28. return string.replace(/\W+/gi, '-').toLowerCase()
  29. }
  30.  
  31. /**
  32. * Apply CSS styles
  33. */
  34. static addStyle (css) {
  35. let style = document.createElement('style')
  36. style.type = 'text/css' // is required
  37. style.innerHTML = css
  38. document.querySelector('head').appendChild(style)
  39. }
  40.  
  41. /**
  42. * @param {String} uid
  43. * @param {Object} data
  44. */
  45. static addTranslation (uid, data) {
  46. if (!data.en) {
  47. console.error('Default translation `en` is required')
  48. }
  49. let locale = I18n.currentLocale()
  50. I18n.translations[locale][uid] = data[locale] || data.en
  51. }
  52. }
  53.  
  54. /**
  55. * God class, create it once
  56. */
  57. class WMEUIHelper {
  58. constructor (uid) {
  59. this.uid = WMEUI.normalize(uid)
  60. this.index = 0
  61. }
  62.  
  63. generateId () {
  64. this.index++
  65. return this.uid + '-' + this.index
  66. }
  67.  
  68. createPanel (title, description = null, attributes = {}) {
  69. return new WMEUIHelperPanel(this.uid, this.generateId(), title, description, attributes)
  70. }
  71.  
  72. createTab (title, description = null, attributes = {}) {
  73. return new WMEUIHelperTab(this.uid, this.generateId(), title, description, attributes)
  74. }
  75.  
  76. createModal (title, description = null) {
  77. return new WMEUIHelperModal(this.uid, this.generateId(), title, description)
  78. }
  79.  
  80. createFieldset (title, description = null) {
  81. return new WMEUIHelperFieldset(this.uid, this.generateId(), title, description)
  82. }
  83. }
  84.  
  85. /**
  86. * Basic for all UI elements
  87. */
  88. class WMEUIHelperElement {
  89. constructor (uid, id, title, description = null, attributes = {}) {
  90. this.uid = uid
  91. this.id = id
  92. this.title = title
  93. this.description = description
  94. this.attributes = attributes
  95. this.domElement = null
  96. }
  97.  
  98. /**
  99. * @param {HTMLElement} element
  100. * @return {HTMLElement}
  101. */
  102. applyAttributes (element) {
  103. for (let attr in this.attributes) {
  104. if (this.attributes.hasOwnProperty(attr)) {
  105. element[attr] = this.attributes[attr]
  106. }
  107. }
  108. return element
  109. }
  110.  
  111. /**
  112. * @return {HTMLElement}
  113. */
  114. html () {
  115. if (!this.domElement) {
  116. this.domElement = this.toHTML()
  117. this.domElement.className += ' ' + this.uid + ' ' + this.uid + '-' + this.id
  118. }
  119. return this.domElement
  120. }
  121.  
  122. /**
  123. * @return {HTMLElement}
  124. */
  125. toHTML () {
  126. throw new Error('Abstract method')
  127. }
  128. }
  129.  
  130.  
  131. /**
  132. * Basic for all UI containers
  133. */
  134. class WMEUIHelperContainer extends WMEUIHelperElement {
  135. constructor (uid, id, title, description = null, attributes = {}) {
  136. super(uid, id, title, description, attributes)
  137. this.elements = []
  138. if (description) {
  139. this.addText('description', description)
  140. }
  141. }
  142.  
  143. addElement (element) {
  144. this.elements.push(element)
  145. }
  146.  
  147. // For Tab Panel Modal Fieldset
  148. addText (id, text) {
  149. return this.addElement(new WMEUIHelperText(this.uid, id, text))
  150. }
  151.  
  152. // For Tab Panel Modal
  153. addFieldset (id, title, description) {
  154. return this.addElement(new WMEUIHelperFieldset(this.uid, id, title, description))
  155. }
  156.  
  157. // For Tab Panel Modal Fieldset
  158. addCheckbox (id, title, description, callback, checked = false) {
  159. return this.addElement(
  160. new WMEUIHelperControlInput(this.uid, id, title, description, {
  161. 'id': this.uid + '-' + id,
  162. 'onclick': callback,
  163. 'type': 'checkbox',
  164. 'value': 1,
  165. 'checked': checked,
  166. })
  167. )
  168. }
  169.  
  170. addRadio (id, title, description, callback, value, checked = false) {
  171. return this.addElement(
  172. new WMEUIHelperControlInput(this.uid, id, title, description, {
  173. 'id': this.uid + '-' + id + '-' + value,
  174. 'onclick': callback,
  175. 'type': 'radio',
  176. 'value': value,
  177. 'checked': checked,
  178. })
  179. )
  180. }
  181.  
  182. addRange (id, title, description, callback, min, max, value, step = 10) {
  183. return this.addElement(
  184. new WMEUIHelperControlInput(this.uid, id, title, description, {
  185. 'id': this.uid + '-' + id,
  186. 'onchange': callback,
  187. 'type': 'range',
  188. 'min': min,
  189. 'max': max,
  190. 'value': value,
  191. 'step': step,
  192. })
  193. )
  194. }
  195.  
  196. // For Tab Panel Modal Fieldset
  197. addButton (id, title, description, callback, shortcut = null) {
  198. return this.addElement(new WMEUIHelperControlButton(this.uid, id, title, description, callback, shortcut))
  199. }
  200.  
  201. addButtons (buttons) {
  202. for (let btn in buttons) {
  203. if (buttons.hasOwnProperty(btn)) {
  204. this.addButton(
  205. btn,
  206. buttons[btn].title,
  207. buttons[btn].description,
  208. buttons[btn].callback,
  209. buttons[btn].shortcut,
  210. )
  211. }
  212. }
  213. }
  214. }
  215.  
  216. class WMEUIHelperFieldset extends WMEUIHelperContainer {
  217. toHTML () {
  218. // Fieldset legend
  219. let legend = document.createElement('legend')
  220. legend.innerHTML = this.title
  221.  
  222. // Container for buttons
  223. let controls = document.createElement('div')
  224. controls.className = 'controls'
  225. // Append buttons to container
  226. this.elements.forEach(element => controls.append(element.html()))
  227.  
  228. let fieldset = document.createElement('fieldset')
  229. fieldset.append(legend, controls)
  230. return fieldset
  231. }
  232. }
  233.  
  234.  
  235. class WMEUIHelperPanel extends WMEUIHelperContainer {
  236. toHTML () {
  237. // Label of the panel
  238. let label = document.createElement('label')
  239. label.className = 'control-label'
  240. label.innerHTML = this.title
  241. // Container for buttons
  242. let controls = document.createElement('div')
  243. controls.className = 'controls'
  244. // Append buttons to panel
  245. this.elements.forEach(element => controls.append(element.html()))
  246. // Build panel
  247. let group = document.createElement('div')
  248. group.className = 'form-group'
  249. group.append(label)
  250. group.append(controls)
  251. return group
  252. }
  253. }
  254.  
  255.  
  256. class WMEUIHelperTab extends WMEUIHelperContainer {
  257. constructor (uid, id, title, description = null, attributes = {}) {
  258. super(uid, id, title, description, attributes)
  259. this.icon = attributes.icon ? attributes.icon : ''
  260. }
  261. container () {
  262. return document.querySelector('.tab-content')
  263. }
  264.  
  265. inject () {
  266. this.container().append(this.html())
  267. }
  268.  
  269. toHTML () {
  270. // Create tab toggler
  271. let li = document.createElement('li')
  272. li.innerHTML = '<a href="#sidepanel-' + this.uid + '" id="' + this.uid + '" data-toggle="tab">' + this.title + '</a>'
  273. document.querySelector('#user-tabs .nav-tabs').append(li)
  274.  
  275. // Label of the panel
  276. let header = document.createElement('div')
  277. header.className = 'panel-header-component settings-header'
  278. header.innerHTML = '<div class="panel-header-component-main">' + this.icon + '<div class="feature-id-container"><wz-overline>' + this.title + '</wz-overline></div></div>'
  279.  
  280. // Container for buttons
  281. let controls = document.createElement('div')
  282. controls.className = 'button-toolbar'
  283.  
  284. // Append buttons to container
  285. this.elements.forEach(element => controls.append(element.html()))
  286.  
  287. // Build form group
  288. let group = document.createElement('div')
  289. group.className = 'form-group'
  290. group.append(header)
  291. group.append(controls)
  292.  
  293. // Section
  294. let pane = document.createElement('div')
  295. pane.id = 'sidepanel-' + this.uid // required by tab toggle, see above
  296. pane.className = 'tab-pane'
  297. pane.append(group)
  298. return pane
  299. }
  300. }
  301.  
  302. class WMEUIHelperModal extends WMEUIHelperContainer {
  303. container () {
  304. return document.getElementById('panel-container')
  305. }
  306.  
  307. inject () {
  308. this.container().append(this.html())
  309. }
  310.  
  311. toHTML () {
  312. // Header and close button
  313. let close = document.createElement('a')
  314. close.className = 'close-panel'
  315. close.onclick = function () {
  316. panel.remove()
  317. }
  318.  
  319. let header = document.createElement('div')
  320. header.className = 'header'
  321. header.innerHTML = this.title
  322. header.prepend(close)
  323.  
  324. // Body
  325. let body = document.createElement('div')
  326. body.className = 'body'
  327.  
  328. // Append buttons to panel
  329. this.elements.forEach(element => body.append(element.html()))
  330.  
  331. // Container
  332. let archivePanel = document.createElement('div')
  333. archivePanel.className = 'archive-panel'
  334. archivePanel.append(header)
  335. archivePanel.append(body)
  336.  
  337. let panel = document.createElement('div')
  338. panel.className = 'panel show'
  339. panel.append(archivePanel)
  340.  
  341. return panel
  342. }
  343. }
  344.  
  345. class WMEUIHelperText extends WMEUIHelperElement {
  346. toHTML () {
  347. let p = document.createElement('p')
  348. p.innerHTML = this.title
  349. return p
  350. }
  351. }
  352.  
  353. class WMEUIHelperControl extends WMEUIHelperElement {
  354. constructor (uid, id, title, description, attributes = {}) {
  355. super(uid, id, title, description, attributes)
  356. this.attributes.name = this.id
  357. }
  358. }
  359.  
  360. class WMEUIHelperControlInput extends WMEUIHelperControl {
  361. toHTML () {
  362. let input = this.applyAttributes(document.createElement('input'))
  363. let label = document.createElement('label')
  364. label.htmlFor = input.id
  365. label.innerHTML = this.title
  366.  
  367. let container = document.createElement('div')
  368. container.title = this.description
  369. container.className = 'controls-container'
  370. container.append(input, label)
  371. return container
  372. }
  373. }
  374.  
  375. class WMEUIHelperControlButton extends WMEUIHelperControl {
  376. constructor (uid, id, title, description, callback, shortcut = null) {
  377. super(uid, id, title, description)
  378. this.callback = callback
  379. if (shortcut) {
  380. /* name, desc, group, title, shortcut, callback, scope */
  381. new WMEUIShortcut(
  382. this.uid + '-' + this.id,
  383. this.description,
  384. this.uid,
  385. this.uid,
  386. shortcut,
  387. this.callback
  388. )
  389. }
  390. }
  391.  
  392. toHTML () {
  393. let button = document.createElement('button')
  394. button.className = 'waze-btn waze-btn-small waze-btn-white'
  395. button.innerHTML = this.title
  396. button.title = this.description
  397. button.onclick = this.callback
  398. return button
  399. }
  400. }
  401.  
  402. /**
  403. * Based on the code from the WazeWrap library
  404. */
  405. class WMEUIShortcut {
  406. /**
  407. * @param {String} name
  408. * @param {String} desc
  409. * @param {String} group
  410. * @param {String} title
  411. * @param {String} shortcut
  412. * @param {Function} callback
  413. * @param {Object} scope
  414. * @return {WMEUIShortcut}
  415. */
  416. constructor (name, desc, group, title, shortcut, callback, scope= null) {
  417. this.name = name
  418. this.desc = desc
  419. this.group = group || 'default'
  420. this.title = title
  421. this.shortcut = {}
  422. this.callback = callback
  423. this.scope = ('object' === typeof scope) ? scope : null
  424.  
  425. /* Setup translation for shortcut */
  426. if (shortcut.length > 0) {
  427. this.shortcut = {[shortcut]:name}
  428. WMEUIShortcut.addTranslation(this.group, this.title, this.name, this.desc)
  429. }
  430.  
  431. /* Try to initialize new group */
  432. this.addGroup()
  433.  
  434. /* Clear existing actions with same name and create new */
  435. this.addAction()
  436.  
  437. /* Try to register new event */
  438. this.addEvent()
  439.  
  440. /* Finally, register the shortcut */
  441. this.registerShortcut()
  442. }
  443.  
  444. /**
  445. * @param {String} group name
  446. * @param {String} title of the shortcut section
  447. * @param {String} name of the shortcut
  448. * @param {String} description of the shortcut
  449. */
  450. static addTranslation(group, title, name, description) {
  451. if (!I18n.translations[I18n.currentLocale()].keyboard_shortcuts.groups[group]) {
  452. I18n.translations[I18n.currentLocale()].keyboard_shortcuts.groups[group] = {
  453. description: title,
  454. members: {
  455. [name]: description
  456. }
  457. }
  458. }
  459. I18n.translations[I18n.currentLocale()].keyboard_shortcuts.groups[group].members[name] = description
  460. }
  461.  
  462. /**
  463. * Determines if the shortcut's action already exists.
  464. * @private
  465. */
  466. doesGroupExist () {
  467. return 'undefined' !== typeof W.accelerators.Groups[this.group]
  468. && 'undefined' !== typeof W.accelerators.Groups[this.group].members
  469. }
  470.  
  471. /**
  472. * Determines if the shortcut's action already exists.
  473. * @private
  474. */
  475. doesActionExist () {
  476. return 'undefined' !== typeof W.accelerators.Actions[this.name]
  477. }
  478.  
  479. /**
  480. * Determines if the shortcut's event already exists.
  481. * @private
  482. */
  483. doesEventExist () {
  484. return 'undefined' !== typeof W.accelerators.events.dispatcher._events[this.name]
  485. && W.accelerators.events.dispatcher._events[this.name].length > 0
  486. && this.callback === W.accelerators.events.dispatcher._events[this.name][0].func
  487. && this.scope === W.accelerators.events.dispatcher._events[this.name][0].obj
  488. }
  489.  
  490. /**
  491. * Creates the shortcut's group.
  492. * @private
  493. */
  494. addGroup () {
  495. if (this.doesGroupExist()) return
  496.  
  497. W.accelerators.Groups[this.group] = []
  498. W.accelerators.Groups[this.group].members = []
  499. }
  500.  
  501. /**
  502. * Registers the shortcut's action.
  503. * @private
  504. */
  505. addAction () {
  506. if (this.doesActionExist()) {
  507. W.accelerators.Actions[this.name] = null
  508. }
  509. W.accelerators.addAction(this.name, { group: this.group })
  510. }
  511.  
  512. /**
  513. * Registers the shortcut's event.
  514. * @private
  515. */
  516. addEvent () {
  517. if (this.doesEventExist()) return
  518. W.accelerators.events.register(this.name, this.scope, this.callback)
  519. }
  520.  
  521. /**
  522. * Registers the shortcut's keyboard shortcut.
  523. * @private
  524. */
  525. registerShortcut () {
  526. W.accelerators._registerShortcuts(this.shortcut)
  527. }
  528. }