您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Opens a PL in an existing WME window/tab.
// ==UserScript== // @name WME PL Jump // @description Opens a PL in an existing WME window/tab. // @version 2020.11.23.01 // @author The_Cre8r and SAR85 // @copyright The_Cre8r and SAR85 // @license CC BY-NC-ND // @grant none // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/ // @namespace https://github.com/TheCre8r/WME-PL-Jump-Release/ // @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js // ==/UserScript== /* global OpenLayers */ /* global W */ /* global $ */ /* global WazeWrap */ (function () { 'use strict'; var app, AppView, jumpInput, JumpView, LinkView, Link, links, LinkCollection, tab, c1, c2, c3; function log(txt) { if ('object' === typeof txt) { //text = JSON.stringify(text); } console.log('PLJ: ' + txt); } /** * Checks for necessary page elements or objects before initializing * script. * @param tries {Number} The number of tries bootstrapping has been * attempted. */ function bootstrap(tries) { tries = tries || 0; if (WazeWrap.Ready && $ && window.Backbone && $('#edit-buttons').length) { init(); } else if (tries < 20) { window.setTimeout(function () { bootstrap(tries + 1); }, 1000); } } /** * Initializes global variables and sets up HTML and event listeners. */ function init() { var tabContent = '<div id="pljumptabcontent"></div>'; if (!$('#pljumpinput').length) { initializeLink(); initializeCollection(); links = new LinkCollection; tab = new WazeWrap.Interface.Tab('PLJump', tabContent, init2); updateAlert(); app = new AppView({ collection: links }); } } function init2() { if (!$('#pljumpinput').length) { initializeViews(); jumpInput = new JumpView({ collection: links }); W.editingMediator.on('change:editingHouseNumbers', function(){ if(!W.editingMediator.attributes.editingHouseNumbers) init2(); }) } FUck(); } function FUck() { log("Checking for WMEFU"); if ($('#_cbShrinkTopBars').length) { initFU(); } else { /** * MutationObserver for WMEFU */ let targetNode = document.getElementById('user-tabs'); // Select the node that will be observed for mutations let config = { attributes:true, childList: true, subtree: true }; // Options for the observer (which mutations to observe) var callback = (function(mutations) { mutations.forEach(function(mutation) { for (let i = 0; i < mutation.addedNodes.length; i++) { let addedNode = mutation.addedNodes[i]; if (addedNode.nodeType === Node.ELEMENT_NODE) { if (addedNode.innerText.includes("FU")) { initFU(); observer.disconnect(); } } } }); }); var observer = new MutationObserver(callback); // Create an observer instance linked to the callback function observer.observe(targetNode, config); // Start observing the target node for configured mutations } } function initFU() { log('WMEFU - Loaded'); //Change height based on WMEFU Settings if (JSON.parse(localStorage.WMEFUSettings).shrinkTopBars && $('#_inpUICompression').find(":selected").text() == "Low") { log('WMEFU - Initial Load for Low Compression'); setTimeout(function () { $('#pljumpinput').css("height","26px"); $('.pljump').css("margin","1px 10px 0px 10px"); return; }, 950); } else if (JSON.parse(localStorage.WMEFUSettings).shrinkTopBars && $('#_inpUICompression').find(":selected").text() == "High") { log('WMEFU - Initial Load for High Compression'); setTimeout(function () { $('#pljumpinput').css("height","19px"); $('.pljump').css("margin","-6px 10px 0px 10px"); return; }, 950); } $('#_cbShrinkTopBars').click(function(){ if ($('#_cbShrinkTopBars').prop('checked')) { log('WMEFU - Shrinking'); $('#pljumpinput').css("height","26px"); $('.pljump').css("margin","1px 10px 0px 10px"); return; } else { log('WMEFU - Restoring'); $('#pljumpinput').css("height","40px"); $('.pljump').css("margin","8px 10px 0px 10px"); return; } }); $( "#_inpUICompression" ).change(function() { if ($('#_inpUICompression').val() == "1") { log('WMEFU - Shrinking to Low Compression'); $('#pljumpinput').css("height","26px"); $('.pljump').css("margin","1px 10px 0px 10px"); return; } else if ($('#_inpUICompression').val() == "0") { log('WMEFU - Restoring to No Compression'); $('#pljumpinput').css("height","40px"); $('.pljump').css("margin","8px 10px 0px 10px"); return; } else if ($('#_inpUICompression').val() == "2") { log('WMEFU - Shrinking to High Compression'); $('#pljumpinput').css("height","19px"); $('.pljump').css("margin","-6px 10px 0px 10px"); return; } }); } function updateAlert() { var version = GM_info.script.version.toString(); var versionChanges = ''; var previousVersion; versionChanges += 'WME PL Jump v' + version + ' changes:\n'; versionChanges += '- Style Changes\n- Compatibility Updates\n'; if (localStorage === void 0) { return; } localStorage.removeItem("pljumplocation"); localStorage.removeItem("WMEPLJLocation"); localStorage.removeItem("pljumpLocation"); previousVersion = localStorage.pljumpVersion; if (version !== previousVersion) { alert(versionChanges); localStorage.pljumpVersion = version; } } function initializeLink() { /** * Class representing PLs. */ Link = Backbone.Model.extend({ defaults: { link: '', lonLat: null, segments: null, mapProblem: null, nodes: null, venues: null, selectionInBounds: false, selectionOnScreen: false, updateRequest: null, zoom: null }, itemsToSelect: [], modelObject: null, /** * Checks whether the PLs items to select are in the data bounds * and in view. */ isSelectionOnScreen: function () { return this.attributes.selectionInBounds && this.attributes.selectionOnScreen; }, /** * Checks whether the PL has items to select or not. */ hasItems: function () { return this.attributes.segments || this.attributes.nodes || this.attributes.venues || this.attributes.updateRequest || this.attributes.mapProblem; }, /** * Parses PL to extract the location and selected item info. */ initialize: function () { var extractedData = {}, link = this.get('link'), lat, lon, lonLat, mapProblem, nodes, segments, updateRequest, venues, zoom; var linktemp = link; if (linktemp.includes("google")){ let google = link.split('@').pop().split(','); lon = google[1]; lat = google[0]; zoom = parseInt(google[2]); link = 'https://www.waze.com/en-US/editor/?lon=' + lon + '&lat=' + lat + '&zoom=' + $(Math.max(0,Math.min(10,(zoom - 12))))[0]; } else if (linktemp.includes("mandrillapp")){ let mandrillapp = link.split('_'); mandrillapp = window.atob(mandrillapp[1]); mandrillapp = mandrillapp.split(/\\/)[0]; link = 'https://www.waze.com/en-US/editor/?' + mandrillapp; } else if (linktemp.includes("livemap")){ let livemap = link.replace("lng", "lon"); link = livemap; } link = decodeURIComponent(link); lat = link.match(/lat=(-?\d+\.\d+)/); lon = link.match(/lon=(-?\d+\.\d+)/); nodes = link.match(/nodes=((\d+,?)+)/); segments = link.match(/segments=((\d+,?)+)/); updateRequest = link.match(/mapUpdateRequest=(\d+)/); mapProblem = link.match(/mapProblem=(\d%2F\d+)/); venues = link.match(/venues=((\d+\.?)+)/); zoom = link.match(/zoom=(\d+)/); extractedData.lat = lat && lat.length === 2 ? parseFloat(lat[1]) : null; extractedData.lon = lon && lon.length === 2 ? parseFloat(lon[1]) : null; extractedData.segments = segments && segments.length > 1 ? segments[1].split(',') : null; extractedData.venues = venues ? venues[1].split(',') : null; extractedData.nodes = nodes && nodes.length > 1 ? nodes[1].split(',') : null; extractedData.updateRequest = updateRequest && updateRequest.length === 2 ? updateRequest[1] : null; extractedData.mapProblem = mapProblem && mapProblem.length === 2 ? mapProblem[1].replace('%2F', '/') : null; extractedData.zoom = zoom && zoom.length === 2 ? parseInt(zoom[1]) : null; this.set({ 'segments': extractedData.segments, 'mapProblem': extractedData.mapProblem, 'nodes': extractedData.nodes, 'venues': extractedData.venues, 'updateRequest': extractedData.updateRequest, 'zoom': extractedData.zoom }); if (extractedData.lon && extractedData.lat) { lonLat = new OpenLayers.LonLat(extractedData.lon, extractedData.lat); lonLat.transform(W.map.displayProjection, W.map.getProjectionObject()); if (W.map.isValidLonLat(lonLat)) { this.set('lonLat', lonLat); } } else { this.set('lonLat', { lon: 'None', lat: 'None' }); } this.on('change:link', this.onLinkChanged); }, /** * Private method to compile WME objects for selection. * @private */ createSelection: function () { var i, itemsToSelect = [], itemType, itemTypes = ['segments', 'nodes', 'venues'], mapBounds = W.map.getExtent(), modelObject, n, selectionInBounds = true, selectionOnScreen = true; var getObject = function (element) { var object = W.model[itemType].getObjectById(element); if (object) { itemsToSelect.push(object); if (!mapBounds.intersectsBounds( object.geometry.getBounds())) { selectionOnScreen = false; } } else { selectionInBounds = false; } }; for (i = 0, n = itemTypes.length; i < n; i++) { itemType = itemTypes[i]; _.each(this.attributes[itemType], getObject, this); } if (this.attributes.updateRequest || this.attributes.mapProblem) { modelObject = W.model.mapUpdateRequests.getObjectById( this.attributes.updateRequest) || W.model.problems.getObjectById( this.attributes.mapProblem) || null; if (!modelObject) { selectionInBounds = false; } else if (!mapBounds.intersectsBounds( modelObject.attributes.geometry.getBounds())) { selectionOnScreen = false; } } this.set({ 'selectionInBounds': selectionInBounds, 'selectionOnScreen': selectionOnScreen }); this.modelObject = modelObject; this.itemsToSelect = itemsToSelect; return this; }, /** * Pans the map to the lat & lon location specified in the PL. * @private */ moveTo: function () { var zoom = this.get('zoom') || W.map.getZoom(); if (this.attributes.lonLat) { W.map.moveTo(this.attributes.lonLat, zoom); } return this; }, /** * Public method to go to a Link and select its objects. */ open: function (forceMove) { this.createSelection(); if (this.hasItems()) { this.select(); if (forceMove || !this.isSelectionOnScreen()) { this.moveTo(); } } else { this.moveTo(); } return this; }, /** * Re-parse the link if it changes. */ onLinkChanged: function () { this.initialize(); }, /** * Selects objects extracted from the PL. * @private */ select: function () { var selectItems = function () { this.createSelection(); if (this.modelObject) { W.commands.execute('problems:show', this.modelObject); } if (this.itemsToSelect.length > 0) { W.selectionManager.setSelectedModels(this.itemsToSelect); } }; WazeWrap.Model.onModelReady(selectItems, this.isSelectionOnScreen(), this); return this; } }); } function initializeCollection() { /** * Class representing collection of PLs. */ LinkCollection = Backbone.Collection.extend({ model: Link, getLinkID: function () { var id = 0; return function () { return id++; }; } () }); } function initializeViews() { /** * Class containing the main app interface and logic. */ AppView = Backbone.View.extend({ collection: null, /** * Gets the stored options from localStorage and returns * them as an object. */ options: function () { var defaultOptions = { 'trackHistory': true, 'trackSelections': false }; var options = localStorage && localStorage.pljOptions; options = options && JSON.parse(options) || defaultOptions; return options; } (), clearButtonCss: { 'margin-bottom': '10px' }, divCss: { 'overflow-y': 'scroll', 'max-height': '400px' }, tableDivCss: { 'overflow-y': 'scroll', 'max-height': '400px', 'width': '292px' }, el: $('#pljumptabcontent'), template: function () { var $div = $('<div/>'), $table = $('<table/>').attr('id', 'plj-history-table'), $clearButton = $('<button/>').attr('id', 'plj-clear-table'). css(this.clearButtonCss).text('Clear all entries'), $trackHistory = $('<div/>').append($('<input type="checkbox" id="plj-track-history"><label>Track map move history</label>')), $trackSelections = $('<div/>').append($('<input type="checkbox" id="plj-track-selections"><label>Track selections</label>')), $infoText = $('<p>Click a table entry below to select it. To force the map to move to the PL location, Ctrl+Click.</p>'); $div.append($trackHistory); $div.append($trackSelections); $div.append($clearButton.wrap('<div>').parent()); $div.append($infoText.wrap('<div>').parent()); $div.append($table.wrap('<div>').parent(). css(this.tableDivCss)); return $div; }, events: { 'click #plj-clear-table': 'onClearTableClicked', 'change #plj-track-history': 'onTrackHistoryChange', 'change #plj-track-selections': 'onTrackSelectionsChange' }, initialize: function () { this.listenTo(this.collection, 'add', this.addLink); this.render(); this.onTrackHistoryChange(); this.onTrackSelectionsChange(); }, render: function () { this.$el.append(this.template()); this.$trackHistory = this.$el.find('#plj-track-history'); this.$trackHistory.prop('checked', this.options['trackHistory']); this.$trackSelections = this.$el.find('#plj-track-selections'); this.$trackSelections.prop('checked', this.options['trackSelections']); this.$table = this.$el.find('#plj-history-table'); this.$clearButton = this.$el.find('#plj-clear-table'); return this; }, /** * Adds a new link to the table. */ addLink: function (link) { var view = new LinkView({ model: link }); this.$table.prepend(view.render().$el); }, /** * Tracks map moves and determines if the location changed * after a move. This filters out zoom-only "moves". */ mapLocationChanged: function () { var lastLocation, locationChanged, newLocation; var getMapLocation = function () { var lonLat = W.map.getCenter(), zoom = W.map.getZoom(); return { lon: lonLat.lon, lat: lonLat.lat, zoom: zoom }; }; var compareLocations = function () { newLocation = getMapLocation(); if (newLocation.lon !== lastLocation.lon || newLocation.lat !== lastLocation.lat) { lastLocation = newLocation; locationChanged = true; } else { locationChanged = false; } }; lastLocation = getMapLocation(); W.map.events.register('moveend', this, compareLocations); return function () { return locationChanged; }; } (), /** * Callback for clearing the link history. */ onClearTableClicked: function (e) { this.$table.find('tr').remove(); this.collection.reset(); }, /** * Callback for map move. */ onMapMove: function () { if (this.mapLocationChanged()) { this.collection.add({ link: $('.WazeControlPermalink a').prop('href') }); } }, /** * Callback for selection change. */ onSelectionChanged: function () { if (this.selectionChanged()) { this.collection.add({ link: $('.WazeControlPermalink a').prop('href') }); } }, /** * Callback for track history checkbox change. */ onTrackHistoryChange: function (e) { var track = this.$trackHistory.prop('checked'); W.map.events.unregister('moveend', this, this.onMapMove); if (track) { W.map.events.register('moveend', this, this.onMapMove); } this.saveOption('trackHistory', track); }, /** * Callback for track selections checkbox change. */ onTrackSelectionsChange: function (e) { var track = this.$trackSelections.prop('checked'); W.selectionManager.events.unregister('selectionchanged', this, this.onSelectionChanged); if (track) { W.selectionManager.events.register('selectionchanged', this, this.onSelectionChanged); } this.saveOption('trackSelections', track); }, /** * Saves app options to localStorage. */ saveOption: function (key, value) { if (localStorage === void 0) { return; } this.options[key] = value; localStorage.pljOptions = JSON.stringify(this.options); }, /** * Tracks selection changes to determine if the same object is * selected consecutively. */ selectionChanged: function () { var lastSelection, newSelection, selectionChanged; var getSelectedItem = function () { return W.selectionManager.hasSelectedFeatures() && W.selectionManager.getSelectedFeatures[0]; }; var compareSelection = function () { newSelection = getSelectedItem(); if (W.selectionManager.hasSelectedFeatures() && lastSelection !== newSelection) { lastSelection = newSelection; selectionChanged = true; } else { selectionChanged = false; } }; lastSelection = getSelectedItem(); W.selectionManager.events.register('selectionchanged', this, compareSelection); return function () { return selectionChanged; }; } () }); /** * Class for displaying link data in a table. */ LinkView = Backbone.View.extend({ tagName: 'tr', tdCss: { 'padding': '5px', 'border': '1px solid gray', 'border-right': 'none', 'cursor': 'pointer' }, linkInfoCss: { 'margin': 0 }, linkRemoveCss: { 'padding': '5px 5px 5px 10px', 'border': '1px solid gray', 'border-left': 'none', 'text-align': 'center', 'font-weight': 'bold' }, template: function () { var $nameCell = $('<td/>'). css(this.tdCss).addClass('plj-link'), $deleteCell = $('<td/>'). css(this.linkRemoveCss). addClass('plj-remove-link'). append($('<a/>').text('X')), attributes = this.model.attributes, lonLat, objectsText = []; if (attributes.lonLat) { lonLat = attributes.lonLat.clone(); lonLat.transform(W.map.getProjectionObject(), W.map.displayProjection); } objectsText.push('<b>Lon:</b> ' + (lonLat ? lonLat.lon.toFixed(3) + '\xB0' : 'None') + ' <b>Lat:</b> ' + (lonLat ? lonLat.lat.toFixed(3) + '\xB0' : 'None') + ' <b>Zoom:</b> ' + (attributes.zoom ? attributes.zoom : 'None')); if (this.model.hasItems()) { if (attributes.updateRequest) { objectsText.push('<b>Update Request:</b> ' + attributes.updateRequest); } if (attributes.mapProblem) { objectsText.push('<b>Map Problem:</b> ' + attributes.mapProblem); } if (attributes.segments) { objectsText.push('<b>Segments:</b> ' + attributes.segments.join(', ')); } if (attributes.nodes) { objectsText.push('<b>Nodes:</b> ' + attributes.nodes.join(', ')); } if (attributes.venues) { objectsText.push('<b>Places:</b> ' + attributes.venues.join(', ')); } } _.each(objectsText, function (text) { $nameCell.append( //$('<p/>').css(this.linkInfoCss).html(text) $('<p/>').html(text) ); }, this); return [$nameCell, $deleteCell]; }, events: { 'click .plj-link': 'onLinkClicked', 'click .plj-remove-link': 'onRemoveClicked' }, initialize: function () { this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'destroy', this.remove); }, render: function () { var cells = this.template(); this.$nameCell = cells[0]; this.$deleteCell = cells[1]; this.$el.empty(); this.$el.append(this.$nameCell).append(this.$deleteCell); return this; }, /** * Callback for clicking a link. */ onLinkClicked: function (e) { var forceMove = e && e.ctrlKey; W.map.events.unregister('moveend', app, app.onMapMove); this.model.open(forceMove); app.onTrackHistoryChange(); }, /** * Callback for removing a link. */ onRemoveClicked: function (e) { this.model.destroy(); } }); /** * View for text input box and buttons for manual PL input. */ JumpView = Backbone.View.extend({ collection: null, tagName: 'div', template: function () { var buttonstyle = ''; var inputstyle = ""; return `<input type="text" id="pljumpinput" style="font-family:'Rubik', 'Boing-light', sans-serif,FontAwesome; display: inline-block; line-height: normal; outline:0px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05); transition: 0.5s all; background-color: #f0f4f6; border-radius: 8px; border: none; padding: 4px 6px 4px 6px; color: #354148; opacity: 1;height:40px;" placeholder=" WME PL Jump"></input>` + '<button id="pljumpbutton-move" class="btn btn-primary" style="margin-bottom: 5px;margin-right: 4px;margin-left: 4px;padding-left:5px;padding-right:5px;" title="Select & Jump"><i class="fa fa-hand-pointer-o" aria-hidden="true"></i></button>'; //'<button id="pljumpbutton" class="btn btn-primary" style="margin-bottom: 5px; padding-left:5px; padding-right:5px;" title="Select & Jump"><i class="fa fa-rocket" aria-hidden="true"></i></button>'; }, events: { 'click #pljumpbutton': 'onJumpClick', 'click #pljumpbutton-move': 'onJumpMoveClick', 'keyup #pljumpinput': 'onInputChanged' }, initialize: function () { this.render(); this.onInputChanged(); }, render: function () { this.$el.html(this.template()); this.$el.addClass("pljump"); this.$el.css({ 'float': 'left', 'width':'213px', 'margin': '8px 10px 0px 10px' }); this.input = this.$el.find('#pljumpinput'); this.jumpButton = this.$el.find('#pljumpbutton'); this.jumpMoveButton = this.$el.find('#pljumpbutton-move'); // Puts it in the topbar $('#edit-buttons > div').prepend(this.$el); }, /** * Callback for clicking the jump button. */ onJumpClick: function (e, forceMove) { var linkText = this.input.val(), newLink; if (linkText) { newLink = this.collection.add({ link: linkText }); newLink.open(forceMove); } this.input.val(''); this.onInputChanged(); }, /** * Callback for clicking the jump and move button. */ onJumpMoveClick: function (e) { this.onJumpClick(e, true); }, /** * Callback for keyup in the input box. * Disables the buttons if the textbox is empty. * If the key is 'enter', triggers the jump button click. */ onInputChanged: function (e) { /* this.jumpButton.prop('disabled', this.input.val() === '' ? true : false); this.jumpMoveButton.prop('disabled', this.input.val() === '' ? true : false); */ if (e && e.keyCode === 13) { this.onJumpClick(); } } }); } bootstrap(); } ());
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址