您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
UI Library for Waze Map Editor Greasy Fork scripts
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/450320/1090056/WME%20UI.js
// ==UserScript== // @name WME UI // @version 0.0.2 // @description UI Library for Waze Map Editor Greasy Fork scripts // @license MIT License // @author Anton Shevchuk // @namespace http://greasyfork.icu/users/227648-anton-shevchuk // @supportURL https://github.com/AntonShevchuk/wme-ui/issues // @match https://www.waze.com/editor* // @match https://www.waze.com/*/editor* // @match https://beta.waze.com/editor* // @match https://beta.waze.com/*/editor* // @exclude https://www.waze.com/user/editor* // @exclude https://beta.waze.com/user/editor* // @icon https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://anton.shevchuk.name&size=64 // @grant none // ==/UserScript== /* jshint esversion: 8 */ /* global W, I18n */ // WARNING: this is unsafe! let unsafePolicy = { createHTML: string => string } // Feature testing if (window.trustedTypes && window.trustedTypes.createPolicy) { unsafePolicy = window.trustedTypes.createPolicy('unsafe', { createHTML: string => string, }); } class WMEUI { /** * Normalize title or UID * @param string * @returns {string} */ static normalize (string) { return string.replace(/\W+/gi, '-').toLowerCase() } /** * Apply CSS styles */ static addStyle (css) { let style = document.createElement('style') style.type = 'text/css' // is required style.innerHTML = unsafePolicy.createHTML(css) document.querySelector('head').appendChild(style) } /** * @param {String} uid * @param {Object} data */ static addTranslation (uid, data) { if (!data.en) { console.error('Default translation `en` is required') } let locale = I18n.currentLocale() I18n.translations[locale][uid] = data[locale] || data.en } } /** * God class, create it once */ class WMEUIHelper { constructor (uid) { this.uid = WMEUI.normalize(uid) this.index = 0 } /** * Generate unque ID * @return {string} */ generateId () { this.index++ return this.uid + '-' + this.index } /** * Create a panel for the sidebar * @param {String} title * @param {String} description * @param {Object} attributes * @return {WMEUIHelperPanel} */ createPanel (title, description = null, attributes = {}) { return new WMEUIHelperPanel(this.uid, this.generateId(), title, description, attributes) } /** * Create a tab for the sidebar * @param {String} title * @param {String} description * @param {Object} attributes * @return {WMEUIHelperTab} */ createTab (title, description = null, attributes = {}) { return new WMEUIHelperTab(this.uid, this.generateId(), title, description, attributes) } /** * Create a modal window * @param {String} title * @param {String} description * @param {Object} attributes * @return {WMEUIHelperModal} */ createModal (title, description = null) { return new WMEUIHelperModal(this.uid, this.generateId(), title, description) } /** * Create a field set * @param {String} title * @param {String} description * @param {Object} attributes * @return {WMEUIHelperFieldset} */ createFieldset (title, description = null) { return new WMEUIHelperFieldset(this.uid, this.generateId(), title, description) } } /** * Basic for all UI elements */ class WMEUIHelperElement { constructor (uid, id, title, description = null, attributes = {}) { this.uid = uid this.id = id this.title = title this.description = description this.attributes = attributes this.domElement = null } /** * @param {HTMLElement} element * @return {HTMLElement} */ applyAttributes (element) { for (let attr in this.attributes) { if (this.attributes.hasOwnProperty(attr)) { element[attr] = this.attributes[attr] } } return element } /** * @return {HTMLElement} */ html () { if (!this.domElement) { this.domElement = this.toHTML() this.domElement.className += ' ' + this.uid + ' ' + this.uid + '-' + this.id } return this.domElement } /** * @return {HTMLElement} */ toHTML () { throw new Error('Abstract method') } } /** * Basic for all UI containers */ class WMEUIHelperContainer extends WMEUIHelperElement { constructor (uid, id, title, description = null, attributes = {}) { super(uid, id, title, description, attributes) this.elements = [] if (description) { this.addText('description', description) } } /** * Add HTMLElement to container * @param {HTMLElement} element */ addElement (element) { this.elements.push(element) } /** * Create and add WMEUIHelperText element * For Tab, Panel, Modal, or Fieldset * @param {String} id * @param {String} text */ addText (id, text) { return this.addElement(new WMEUIHelperText(this.uid, id, text)) } /** * Create and add fieldset element * For Tab, Panel, Modal * @param {String} id * @param {String} title * @param {String} description */ addFieldset (id, title, description) { return this.addElement(new WMEUIHelperFieldset(this.uid, id, title, description)) } /** * Create checkbox * For Tab, Panel, Modal, or Fieldset * @param {String} id * @param {String} title * @param {String} description * @param {Function} callback * @param {Bool} checked */ addCheckbox (id, title, description, callback, checked = false) { return this.addElement( new WMEUIHelperControlInput(this.uid, id, title, description, { 'id': this.uid + '-' + id, 'onclick': callback, 'type': 'checkbox', 'value': 1, 'checked': checked, }) ) } /** * Create radiobutton * @param {String} id * @param {String} title * @param {String} description * @param {Function} callback * @param {String} value * @param {Bool} checked */ addRadio (id, title, description, callback, value, checked = false) { return this.addElement( new WMEUIHelperControlInput(this.uid, id, title, description, { 'id': this.uid + '-' + id + '-' + value, 'onclick': callback, 'type': 'radio', 'value': value, 'checked': checked, }) ) } /** * Create range input * @param {String} id * @param {String} title * @param {String} description * @param {Function} callback * @param {Integer} min * @param {Integer} max * @param {Integer} value * @param {Integer} step */ addRange (id, title, description, callback, min, max, value, step = 10) { return this.addElement( new WMEUIHelperControlInput(this.uid, id, title, description, { 'id': this.uid + '-' + id, 'onchange': callback, 'type': 'range', 'min': min, 'max': max, 'value': value, 'step': step, }) ) } /** * Create and add button * For Tab Panel Modal Fieldset * @param {String} id * @param {String} title * @param {String} description * @param {Function} callback * @param {String} shortcut */ addButton (id, title, description, callback, shortcut = null) { return this.addElement(new WMEUIHelperControlButton(this.uid, id, title, description, callback, shortcut)) } /** * Create buttons * @param {Object} buttons */ addButtons (buttons) { for (let btn in buttons) { if (buttons.hasOwnProperty(btn)) { this.addButton( btn, buttons[btn].title, buttons[btn].description, buttons[btn].callback, buttons[btn].shortcut, ) } } } } class WMEUIHelperFieldset extends WMEUIHelperContainer { toHTML () { // Fieldset legend let legend = document.createElement('legend') legend.innerHTML = unsafePolicy.createHTML(this.title) // Container for buttons let controls = document.createElement('div') controls.className = 'controls' // Append buttons to container this.elements.forEach(element => controls.append(element.html())) let fieldset = document.createElement('fieldset') fieldset.append(legend, controls) return fieldset } } class WMEUIHelperPanel extends WMEUIHelperContainer { toHTML () { // Label of the panel let label = document.createElement('label') label.className = 'control-label' label.innerHTML = unsafePolicy.createHTML(this.title) // Container for buttons let controls = document.createElement('div') controls.className = 'controls' // Append buttons to panel this.elements.forEach(element => controls.append(element.html())) // Build panel let group = document.createElement('div') group.className = 'form-group' group.append(label) group.append(controls) return group } } class WMEUIHelperTab extends WMEUIHelperContainer { constructor (uid, id, title, description = null, attributes = {}) { super(uid, id, title, description, attributes) this.icon = attributes.icon ? attributes.icon : '' } container () { return document.querySelector('.tab-content') } inject () { this.container().append(this.html()) } toHTML () { // Create tab toggler let li = document.createElement('li') li.innerHTML = unsafePolicy.createHTML( '<a href="#sidepanel-' + this.uid + '" id="' + this.uid + '" data-toggle="tab">' + this.title + '</a>' ) document.querySelector('#user-tabs .nav-tabs').append(li) // Label of the panel let header = document.createElement('div') header.className = 'panel-header-component settings-header' header.innerHTML = unsafePolicy.createHTML( '<div class="panel-header-component-main">' + this.icon + '<div class="feature-id-container"><wz-overline>' + this.title + '</wz-overline></div></div>' ) // Container for buttons let controls = document.createElement('div') controls.className = 'button-toolbar' // Append buttons to container this.elements.forEach(element => controls.append(element.html())) // Build form group let group = document.createElement('div') group.className = 'form-group' group.append(header) group.append(controls) // Section let pane = document.createElement('div') pane.id = 'sidepanel-' + this.uid // required by tab toggle, see above pane.className = 'tab-pane' pane.append(group) return pane } } class WMEUIHelperModal extends WMEUIHelperContainer { container () { return document.getElementById('panel-container') } inject () { this.container().append(this.html()) } toHTML () { // Header and close button let close = document.createElement('a') close.className = 'close-panel' close.onclick = function () { panel.remove() } let header = document.createElement('div') header.className = 'header' header.innerHTML = unsafePolicy.createHTML(this.title) header.prepend(close) // Body let body = document.createElement('div') body.className = 'body' // Append buttons to panel this.elements.forEach(element => body.append(element.html())) // Container let archivePanel = document.createElement('div') archivePanel.className = 'archive-panel' archivePanel.append(header) archivePanel.append(body) let panel = document.createElement('div') panel.className = 'panel show' panel.append(archivePanel) return panel } } class WMEUIHelperText extends WMEUIHelperElement { toHTML () { let p = document.createElement('p') p.innerHTML = unsafePolicy.createHTML(this.title) return p } } class WMEUIHelperControl extends WMEUIHelperElement { constructor (uid, id, title, description, attributes = {}) { super(uid, id, title, description, attributes) this.attributes.name = this.id } } class WMEUIHelperControlInput extends WMEUIHelperControl { toHTML () { let input = this.applyAttributes(document.createElement('input')) let label = document.createElement('label') label.htmlFor = input.id label.innerHTML = unsafePolicy.createHTML(this.title) let container = document.createElement('div') container.title = this.description container.className = 'controls-container' container.append(input, label) return container } } class WMEUIHelperControlButton extends WMEUIHelperControl { constructor (uid, id, title, description, callback, shortcut = null) { super(uid, id, title, description) this.callback = callback if (shortcut) { /* name, desc, group, title, shortcut, callback, scope */ new WMEUIShortcut( this.uid + '-' + this.id, this.description, this.uid, this.uid, shortcut, this.callback ) } } toHTML () { let button = document.createElement('button') button.className = 'waze-btn waze-btn-small waze-btn-white' button.innerHTML = unsafePolicy.createHTML(this.title) button.title = this.description button.onclick = this.callback return button } } /** * Based on the code from the WazeWrap library */ class WMEUIShortcut { /** * @param {String} name * @param {String} desc * @param {String} group * @param {String} title * @param {String} shortcut * @param {Function} callback * @param {Object} scope * @return {WMEUIShortcut} */ constructor (name, desc, group, title, shortcut, callback, scope = null) { this.name = name this.desc = desc this.group = group || 'default' this.title = title this.shortcut = {} this.callback = callback this.scope = ('object' === typeof scope) ? scope : null /* Setup translation for shortcut */ if (shortcut.length > 0) { this.shortcut = { [shortcut]: name } WMEUIShortcut.addTranslation(this.group, this.title, this.name, this.desc) } /* Try to initialize new group */ this.addGroup() /* Clear existing actions with same name and create new */ this.addAction() /* Try to register new event */ this.addEvent() /* Finally, register the shortcut */ this.registerShortcut() } /** * @param {String} group name * @param {String} title of the shortcut section * @param {String} name of the shortcut * @param {String} description of the shortcut */ static addTranslation (group, title, name, description) { if (!I18n.translations[I18n.currentLocale()].keyboard_shortcuts.groups[group]) { I18n.translations[I18n.currentLocale()].keyboard_shortcuts.groups[group] = { description: title, members: { [name]: description } } } I18n.translations[I18n.currentLocale()].keyboard_shortcuts.groups[group].members[name] = description } /** * Determines if the shortcut's action already exists. * @private */ doesGroupExist () { return 'undefined' !== typeof W.accelerators.Groups[this.group] && 'undefined' !== typeof W.accelerators.Groups[this.group].members } /** * Determines if the shortcut's action already exists. * @private */ doesActionExist () { return 'undefined' !== typeof W.accelerators.Actions[this.name] } /** * Determines if the shortcut's event already exists. * @private */ doesEventExist () { return 'undefined' !== typeof W.accelerators.events.dispatcher._events[this.name] && W.accelerators.events.dispatcher._events[this.name].length > 0 && this.callback === W.accelerators.events.dispatcher._events[this.name][0].func && this.scope === W.accelerators.events.dispatcher._events[this.name][0].obj } /** * Creates the shortcut's group. * @private */ addGroup () { if (this.doesGroupExist()) return W.accelerators.Groups[this.group] = [] W.accelerators.Groups[this.group].members = [] } /** * Registers the shortcut's action. * @private */ addAction () { if (this.doesActionExist()) { W.accelerators.Actions[this.name] = null } W.accelerators.addAction(this.name, { group: this.group }) } /** * Registers the shortcut's event. * @private */ addEvent () { if (this.doesEventExist()) return W.accelerators.events.register(this.name, this.scope, this.callback) } /** * Registers the shortcut's keyboard shortcut. * @private */ registerShortcut () { W.accelerators._registerShortcuts(this.shortcut) } }