您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Easily update roads
// ==UserScript== // @name WME EZSegments // @namespace https://greasyfork.org/en/scripts/518381-wme-ezsegments // @version 2.1 // @description Easily update roads // @author https://github.com/michaelrosstarr // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor.*$/ // @exclude https://www.waze.com/user/*editor/* // @exclude https://www.waze.com/*/user/*editor/* // @grant GM_getValue // @grant GM_setValue // @icon https://www.google.com/s2/favicons?sz=64&domain=waze.com // @grant none // @license GNU GPL(v3) // ==/UserScript== const ScriptName = GM_info.script.name; const ScriptVersion = GM_info.script.version; let wmeSDK; const roadTypes = [ { id: 1, name: 'Street', value: 1 }, { id: 2, name: 'Primary Street', value: 2 }, { id: 3, name: 'Freeway', value: 3 }, { id: 4, name: 'Ramp', value: 4 }, { id: 5, name: 'Walking Trail', value: 5 }, { id: 6, name: 'Major Highway', value: 6 }, { id: 7, name: 'Minor Freeway', value: 7 }, { id: 8, name: 'Offroad', value: 8 }, { id: 9, name: 'Walkway', value: 9 }, { id: 10, name: 'Pedestrian Walkway', value: 10 }, { id: 11, name: 'Ferry', value: 15 }, { id: 12, name: 'Stairway', value: 16 }, { id: 13, name: 'Private Road', value: 17 }, { id: 14, name: 'Railroad', value: 18 }, { id: 15, name: 'Runway/Taxiway', value: 19 }, { id: 16, name: 'Parking Lot Road', value: 20 }, { id: 17, name: 'Alley', value: 25 }, ]; const defaultOptions = { roadType: 1, unpaved: false, setStreet: false, autosave: false, setSpeed: 60, setLock: false, updateSpeed: false, locks: roadTypes.map(roadType => ({ id: roadType.id, lock: 1 })), speeds: roadTypes.map(roadType => ({ id: roadType.id, speed: 60 })) }; const locks = [ { id: 1, value: 1 }, { id: 2, value: 2 }, { id: 3, value: 3 }, { id: 4, value: 4 }, { id: 5, value: 5 }, { id: 6, value: 6 }, ] const log = (message) => { if (typeof message === 'string') { console.log('WME_EZRoads: ' + message); } else { console.log('WME_EZRoads: ', message); } } window.SDK_INITIALIZED.then(initScript); function initScript() { wmeSDK = getWmeSdk({ scriptId: "wme-ez-segments", scriptName: "EZ Segments" }); WME_EZRoads_bootstrap(); } const getCurrentCountry = () => { return wmeSDK.DataModel.Countries.getTopCountry(); } const getTopCity = () => { return wmeSDK.DataModel.Cities.getTopCity(); } const getAllCities = () => { return wmeSDK.DataModel.Cities.getAll(); } const saveOptions = (options) => { window.localStorage.setItem('WME_EZRoads_Options', JSON.stringify(options)); } const getOptions = () => { const savedOptions = JSON.parse(window.localStorage.getItem('WME_EZRoads_Options')) || {}; // Merge saved options with defaults to ensure all expected options exist return { ...defaultOptions, ...savedOptions }; } const WME_EZRoads_bootstrap = () => { if ( !document.getElementById('edit-panel') || !wmeSDK.DataModel.Countries.getTopCountry() ) { setTimeout(WME_EZRoads_bootstrap, 250); return; } if (wmeSDK.State.isReady) { WME_EZRoads_init(); } else { wmeSDK.Events.once({ eventName: 'wme-ready' }).then(WME_EZRoads_init()); } } let openPanel; const WME_EZRoads_init = () => { log("Initing"); const roadObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { for (let i = 0; i < mutation.addedNodes.length; i++) { const addedNode = mutation.addedNodes[i]; if (addedNode.nodeType === Node.ELEMENT_NODE) { let editSegment = addedNode.querySelector('#segment-edit-general'); if (editSegment) { openPanel = editSegment; // Check if THIS SPECIFIC panel already has the button const parentElement = editSegment.parentNode; if (!parentElement.querySelector('[data-ez-road-button="true"]')) { log("Creating Quick Set Road button for this panel"); const quickButton = document.createElement('wz-button'); quickButton.setAttribute('type', 'button'); quickButton.setAttribute('style', 'margin-bottom: 5px; width: 100%'); quickButton.setAttribute('disabled', 'false'); quickButton.setAttribute('data-ez-road-button', 'true'); quickButton.setAttribute('id', 'ez-road-quick-button-' + Date.now()); // Unique ID using timestamp quickButton.classList.add('send-button', 'ez-comment-button'); quickButton.textContent = 'Quick Set Road'; parentElement.insertBefore(quickButton, editSegment); quickButton.addEventListener('mousedown', () => handleUpdate()); log("Button created for current panel"); } else { log("This panel already has the button, skipping creation"); } } } } }); }); roadObserver.observe(document.getElementById('edit-panel'), { childList: true, subtree: true }); constructSettings(); document.addEventListener("keydown", (event) => { // Check if the active element is an input or textarea const isInputActive = document.activeElement && ( document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.contentEditable === 'true' || document.activeElement.tagName === 'WZ-AUTOCOMPLETE' || document.activeElement.tagName === 'WZ-TEXTAREA' ); log(document.activeElement.tagName); log(isInputActive); // Only trigger the update if the active element is not an input or textarea if (!isInputActive && event.key.toLowerCase() === "u") { handleUpdate(); } }); log("Completed Init") } const getEmptyStreet = () => { } const getEmptyCity = () => { return wmeSDK.DataModel.Cities.getCity({ cityName: '', countryId: getCurrentCountry().id }) || wmeSDK.DataModel.Cities.addCity({ cityName: '', countryId: getCurrentCountry().id }); } const handleUpdate = () => { const selection = wmeSDK.Editing.getSelection(); if (!selection || selection.objectType !== 'segment') return; log('Updating RoadType'); const options = getOptions(); selection.ids.forEach(id => { // Road Type if (options.roadType) { const seg = wmeSDK.DataModel.Segments.getById({ segmentId: id }); if (seg.roadType !== options.roadType) { wmeSDK.DataModel.Segments.updateSegment({ segmentId: id, roadType: options.roadType }); } } // Set lock if enabled if (options.setLock) { const rank = wmeSDK.State.getUserInfo().rank; const selectedRoad = roadTypes.find(rt => rt.value === options.roadType); if (selectedRoad) { const lockSetting = options.locks.find(l => l.id === selectedRoad.id); if (lockSetting) { let toLock = lockSetting.lock - 1; if (rank < toLock) toLock = rank; log(toLock); wmeSDK.DataModel.Segments.updateSegment({ segmentId: id, lockRank: toLock // Changed from hardcoded value 2 to use the calculated lock level }); } } } // Speed Limit - use road-specific speed if updateSpeed is enabled if (options.updateSpeed) { const selectedRoad = roadTypes.find(rt => rt.value === options.roadType); if (selectedRoad) { const speedSetting = options.speeds.find(s => s.id === selectedRoad.id); log('Selected road for speed: ' + selectedRoad.name); log('Speed setting found: ' + (speedSetting ? 'yes' : 'no')); if (speedSetting) { const speedValue = parseInt(speedSetting.speed, 10); log('Speed value to set: ' + speedValue); // Apply speed if it's a valid number (including 0) if (!isNaN(speedValue) && speedValue >= 0) { log('Applying speed: ' + speedValue); wmeSDK.DataModel.Segments.updateSegment({ segmentId: id, fwdSpeedLimit: speedValue, revSpeedLimit: speedValue }); } else { log('Not applying speed - invalid value: ' + speedSetting.speed); } } } } else { log('Speed updates disabled'); } // Handling the street if (options.setStreet) { let city; let street; city = getTopCity() || getEmptyCity(); street = wmeSDK.DataModel.Streets.getStreet({ cityId: city.id, streetName: '', }); log(`City ${city.id}`); if (!street) { street = wmeSDK.DataModel.Streets.addStreet({ streetName: '', cityId: city.id }); } wmeSDK.DataModel.Segments.updateAddress({ segmentId: id, primaryStreetId: street.id }) } log(options); // Updated unpaved handler with fallback if (options.unpaved) { // First try the new method - look for the unpaved chip using the icon class const unpavedIcon = openPanel.querySelector('.w-icon-unpaved-fill'); let unpavedToggled = false; if (unpavedIcon) { // Click the parent wz-checkable-chip element const unpavedChip = unpavedIcon.closest('wz-checkable-chip'); if (unpavedChip) { unpavedChip.click(); log('Clicked unpaved chip'); unpavedToggled = true; } } // If new method failed, try the old method as fallback if (!unpavedToggled) { try { const wzCheckbox = openPanel.querySelector('wz-checkbox[name="unpaved"]'); if (wzCheckbox) { const hiddenInput = wzCheckbox.querySelector('input[type="checkbox"][name="unpaved"]'); if (hiddenInput) { hiddenInput.click(); log('Clicked unpaved checkbox (fallback method)'); unpavedToggled = true; } } } catch (e) { log('Fallback unpaved toggle method failed: ' + e); } } if (!unpavedToggled) { log('Could not toggle unpaved setting - no compatible elements found'); } } }) // Autosave if (options.autosave) { wmeSDK.Editing.save().then(() => { }); } } const constructSettings = () => { const localOptions = getOptions(); let currentRoadType = localOptions.roadType; const update = (key, value) => { const options = getOptions(); options[key] = value; localOptions[key] = value; saveOptions(options); }; // Update lock level for a specific road type const updateLockLevel = (roadTypeId, lockLevel) => { const options = getOptions(); const lockIndex = options.locks.findIndex(l => l.id === roadTypeId); if (lockIndex !== -1) { options.locks[lockIndex].lock = parseInt(lockLevel); localOptions.locks = options.locks; saveOptions(options); } }; // Update speed for a specific road type const updateSpeed = (roadTypeId, speed) => { const options = getOptions(); const speedIndex = options.speeds.findIndex(s => s.id === roadTypeId); // Make sure we have a valid integer let speedValue = parseInt(speed, 10); if (isNaN(speedValue)) { speedValue = -1; // Default to -1 for invalid values } log(`Updating speed for road type ${roadTypeId} to ${speedValue}`); if (speedIndex !== -1) { options.speeds[speedIndex].speed = speedValue; localOptions.speeds = options.speeds; saveOptions(options); } }; // Reset all options to defaults const resetOptions = () => { saveOptions(defaultOptions); // Refresh the page to reload settings window.location.reload(); }; // Checkbox option definitions const checkboxOptions = [ { id: 'setStreet', text: 'Set Street To None', key: 'setStreet' }, { id: 'autosave', text: 'Autosave on Action', key: 'autosave' }, { id: 'unpaved', text: 'Set Road as Unpaved', key: 'unpaved' }, { id: 'setLock', text: 'Set the lock to the level', key: 'setLock' }, { id: 'updateSpeed', text: 'Update speed limits', key: 'updateSpeed' } ]; // Helper function to create radio buttons const createRadioButton = (roadType) => { const id = `road-${roadType.id}`; const isChecked = localOptions.roadType === roadType.value; const lockSetting = localOptions.locks.find(l => l.id === roadType.id) || { id: roadType.id, lock: 1 }; const speedSetting = localOptions.speeds.find(s => s.id === roadType.id) || { id: roadType.id, speed: 60 }; const div = $(`<div class="ezroads-option"> <div class="ezroads-radio-container"> <input type="radio" id="${id}" name="defaultRoad" ${isChecked ? 'checked' : ''}> <label for="${id}">${roadType.name}</label> <select id="lock-level-${roadType.id}" class="road-lock-level" data-road-id="${roadType.id}" ${!localOptions.setLock ? 'disabled' : ''}> ${locks.map(lock => `<option value="${lock.value}" ${lockSetting.lock === lock.value ? 'selected' : ''}>L${lock.value}</option>`).join('')} </select> <input type="number" id="speed-${roadType.id}" class="road-speed" data-road-id="${roadType.id}" value="${speedSetting.speed}" min="-1" ${!localOptions.updateSpeed ? 'disabled' : ''}> </div> </div>`); div.find('input[type="radio"]').on('click', () => { update('roadType', roadType.value); currentRoadType = roadType.value; }); div.find('select').on('change', function () { updateLockLevel(roadType.id, $(this).val()); }); div.find('input.road-speed').on('change', function () { // Get the value as a number const speedValue = parseInt($(this).val(), 10); // If it's not a number, reset to 0 if (isNaN(speedValue)) { $(this).val(0); updateSpeed(roadType.id, 0); } else { updateSpeed(roadType.id, speedValue); } }); return div; }; // Helper function to create checkboxess'; const createCheckbox = (option) => { const isChecked = localOptions[option.key]; const div = $(`<div class="ezroads-option"> <input type="checkbox" id="${option.id}" name="${option.id}" ${isChecked ? 'checked' : ''}> <label for="${option.id}">${option.text}</label> </div>`); div.on('click', () => update(option.key, $(`#${option.id}`).prop('checked'))); return div; }; // Register the script tab wmeSDK.Sidebar.registerScriptTab().then(({ tabLabel, tabPane }) => { tabLabel.innerText = 'EZ Segments'; tabLabel.title = 'Easily Update Roads'; // Setup base styles const styles = $(`<style> #ezroads-settings h2, #ezroads-settings h5 { margin-top: 0; margin-bottom: 10px; } .ezroads-section { margin-bottom: 15px; } .ezroads-option { margin-bottom: 8px; } .ezroads-radio-container { display: flex; align-items: center; } .ezroads-radio-container input[type="radio"] { margin-right: 5px; } .ezroads-radio-container label { flex: 1; margin-right: 10px; text-align: left; } .ezroads-radio-container select { width: 80px; margin-left: auto; margin-right: 5px; } .ezroads-radio-container input.road-speed { width: 60px; } .ezroads-reset-button { margin-top: 20px; padding: 8px 12px; background-color: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; } .ezroads-reset-button:hover { background-color: #d32f2f; } </style>`); tabPane.innerHTML = '<div id="ezroads-settings"></div>'; const scriptContentPane = $('#ezroads-settings'); scriptContentPane.append(styles); // Header section const header = $(`<div class="ezroads-section"> <h2>EZ Segments</h2> <div>Current Version: <b>${ScriptVersion}</b></div> <div>Update Keybind: <kbd>u</kbd></div> </div>`); scriptContentPane.append(header); // Road type and options header const roadTypeHeader = $(`<div class="ezroads-section"> <div style="display: flex; align-items: center;"> <div style="flex-grow: 1; text-align: center;">Road Type</div> <div style="width: 80px; text-align: center;">Lock</div> <div style="width: 60px; text-align: center;">Speed</div> </div> </div>`); scriptContentPane.append(roadTypeHeader); // Road type section with header const roadTypeSection = $(`<div class="ezroads-section"> <div id="road-type-options"></div> </div>`); scriptContentPane.append(roadTypeSection); const roadTypeOptions = roadTypeSection.find('#road-type-options'); roadTypes.forEach(roadType => { roadTypeOptions.append(createRadioButton(roadType)); }); // Additional options section const additionalSection = $(`<div class="ezroads-section"> <h5>Additional Options</h5> <div id="additional-options"></div> </div>`); scriptContentPane.append(additionalSection); const additionalOptions = additionalSection.find('#additional-options'); checkboxOptions.forEach(option => { additionalOptions.append(createCheckbox(option)); }); // Update all lock dropdowns when setLock checkbox changes $(document).on('click', '#setLock', function () { const isChecked = $(this).prop('checked'); $('.road-lock-level').prop('disabled', !isChecked); }); // Update all speed inputs when updateSpeed checkbox changes $(document).on('click', '#updateSpeed', function () { const isChecked = $(this).prop('checked'); $('.road-speed').prop('disabled', !isChecked); log('Speed update option changed to: ' + isChecked); }); // Remove the separate lock levels section // Reset button section const resetButton = $(`<button class="ezroads-reset-button">Reset All Options</button>`); resetButton.on('click', function () { if (confirm('Are you sure you want to reset all options to default values?')) { resetOptions(); } }); scriptContentPane.append(resetButton); }); };