// ==UserScript== // @name WME EZRoad Mod // @namespace https://greasyfork.org/users/1087400 // @version 0.2.3 // @description Easily update roads // @author https://github.com/michaelrosstarr, https://greasyfork.org/en/users/1087400-kid4rm90s // @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 MIT // @downloadURL none // ==/UserScript== /*Script modified from WME EZRoad (https://greasyfork.org/en/scripts/518381-wme-ezroad) original author: Michaelrosstarr and thanks to him*/ /*********************Changelog************************ Version 0.2.1 Date 2025.03.01 Added MWY, MH, mH Added lock level MWY-5, MH-4, mH-3, PS-2, St-1 added support for autosave added support for pave/unpaved Version 0.2.2 Date 2025.03.02 added support for pave/unpaved in both compact and non compact mode. Version 0.2.3 code sync and cleanup unnecessary lines *******************************************************/ const ScriptName = GM_info.script.name; const ScriptVersion = GM_info.script.version; let wmeSDK; const defaultOptions = { roadType: 1, lockRank: 0, unpaved: false, setStreet: false, autosave: false, setSpeed: 40 }; const log = (message) => { if (typeof message === 'string') { console.log('WME_EZRoads_Mod: ' + message); } else { console.log('WME_EZRoads_Mod: ', message); } } window.SDK_INITIALIZED.then(initScript); function initScript() { wmeSDK = getWmeSdk({ scriptId: "wme-ez-roads-mod", scriptName: "EZ Roads Mod" }); WME_EZRoads_Mod_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_Mod_Options', JSON.stringify(options)); } const getOptions = () => { const savedOptions = JSON.parse(window.localStorage.getItem('WME_EZRoads_Mod_Options')) || {}; // Merge saved options with defaults to ensure all expected options exist return { ...defaultOptions, ...savedOptions }; } const WME_EZRoads_Mod_bootstrap = () => { if ( !document.getElementById('edit-panel') || !wmeSDK.DataModel.Countries.getTopCountry() ) { setTimeout(WME_EZRoads_Mod_bootstrap, 250); return; } if (wmeSDK.State.isReady) { WME_EZRoads_Mod_init(); } else { wmeSDK.Events.once({ eventName: 'wme-ready' }).then(WME_EZRoads_Mod_init()); } } let openPanel; const WME_EZRoads_Mod_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; const quickButton = document.createElement('wz-button'); quickButton.setAttribute('type', 'button'); quickButton.setAttribute('style', 'margin-bottom: 5px; width: 100%'); quickButton.setAttribute('disabled', 'false'); quickButton.classList.add('send-button', 'ez-comment-button'); quickButton.textContent = 'Quick Set Road'; editSegment.parentNode.insertBefore(quickButton, editSegment); quickButton.addEventListener('mousedown', () => handleUpdate()); } } } }); }); 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() === "g") { 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'); log('Updating LockRank'); const options = getOptions(); log('options.unpaved:', options.unpaved); // Debug: Log options.unpaved value selection.ids.forEach(id => { /* // handling compact mode and non compact mode const isCompactMode = document.body.classList.contains('compact-density'); log(`Detected Mode (Class Check): ${isCompactMode ? 'Compact' : 'Non-Compact'}`); if (isCompactMode) { log('Operating in Compact Mode (Chip)'); // Unpaved Chip handling - SIMPLE LOGIC based on scenarios - CORRECTED Scenario 2 Logic v0.2.0 const unpavedChip = openPanel.querySelector('wz-checkable-chip i.w-icon-unpaved-fill').closest('wz-checkable-chip'); log('unpavedChip Element (Compact):', unpavedChip); if (unpavedChip) { const isCurrentlyUnpaved = unpavedChip.hasAttribute('checked'); if (options.unpaved) { // Scenario 1: "Set Road as Unpaved" is ticked log('Scenario 1 (Compact Chip): Set Road as Unpaved is TICKED'); if (isCurrentlyUnpaved) { // If currently PAVED, change to UNPAVED unpavedChip.click(); log('Scenario 1 (Compact Chip): Changed Paved to Unpaved (clicked chip).'); } } else { // Scenario 2: "Set Road as Unpaved" is UNticked (set as Paved) log('Scenario 2 (Compact Chip): Set Road as Unpaved is UNTICKED (set as Paved)'); if (!isCurrentlyUnpaved) { // If currently PAVED, change to UNPAVED unpavedChip.click(); log('Scenario 2 (Compact Chip): Changed Unpaved to Paved (clicked chip).'); } else { log('Scenario 2 (Compact Chip): Already Paved (no chip click needed).'); } } } else { log('Compact Mode: unpavedChip element NOT FOUND.'); } } else { // Non-Compact Mode log('Operating in Non-Compact Mode (Checkbox)'); // Checkbox handling - SIMPLE LOGIC (as per user request) if (options.unpaved) { const wzCheckbox = openPanel.querySelector('wz-checkbox[name="unpaved"]'); log('wzCheckbox Element (Non-Compact):', wzCheckbox); const hiddenInput = wzCheckbox.querySelector('input[type="checkbox"][name="unpaved"]'); hiddenInput.click(); log('Scenario (Non-Compact Checkbox): Set Unpaved based on options.unpaved (clicked checkbox).'); } else { log('Scenario (Non-Compact Checkbox): options.unpaved is UNTICKED, no checkbox action in this simplified logic.'); // If you need to handle setting to "paved" in non-compact mode when options.unpaved is false, add logic here. } }*/ // Road Type setTimeout(() => { if (options.roadType) { const seg = wmeSDK.DataModel.Segments.getById({segmentId: id}); if(seg.roadType !== options.roadType) { log(`Segment ID: ${id}, Current Road Type: ${seg.roadType}, Target Road Type: ${options.roadType}`); // Log current and target road type try { wmeSDK.DataModel.Segments.updateSegment({segmentId: id, roadType: options.roadType}); log('Road type updated successfully.'); } catch (error) { console.error('Error updating road type:', error); } } } }, 200); // 200ms delay before road type update // Lock Rank setTimeout(() => { if (options.lockRank) { const seg = wmeSDK.DataModel.Segments.getById({segmentId: id}); if(seg.lockRank !== options.lockRank) { log(`Segment ID: ${id}, Current Lock Rank: ${seg.lockRank}, Target Lock Rank: ${options.lockRank}`); // Log current and target lock rank try { wmeSDK.DataModel.Segments.updateSegment({segmentId: id, lockRank: options.lockRank}); log('Road Rank updated successfully.'); } catch (error) { console.error('Error updating road Rank:', error); } } } }, 250); // 250ms delay before lock rank update /* setTimeout(() => { if (options.lockRank !== null && options.lockRank !== undefined) { const seg = wmeSDK.DataModel.Segments.getById({segmentId: id}); console.log(new Date().toISOString() + " - WME_EZRoads_Mod: Segment ID:", id); console.log(new Date().toISOString() + " - WME_EZRoads_Mod: Current Segment Lock Rank:", seg.lockRank); console.log(new Date().toISOString() + " - WME_EZRoads_Mod: Target Lock Rank (options.lockRank):", options.lockRank); //console.log(new Date().toISOString() + " - WME_EZRoads_Mod: Full Segment Object:", seg); // Log full object temporarily try { console.log(new Date().toISOString() + " - WME_EZRoads_Mod: Attempting to update lockRank..."); wmeSDK.DataModel.Segments.updateSegment({segmentId: id, lockRank: options.lockRank}); console.log(new Date().toISOString() + " - WME_EZRoads_Mod: Lock Rank updated successfully for Segment ID:", id); } catch (error) { console.error(new Date().toISOString() + " - WME_EZRoads_Mod: Error updating lock rank for Segment ID:", id, error); console.log(new Date().toISOString() + " - WME_EZRoads_Mod: updateSegment call FAILED for lockRank for Segment ID:", id, error); } } }, 250); // 250ms delay before lock rank update */ // Speed Limit if(options.setSpeed != -1) { wmeSDK.DataModel.Segments.updateSegment({ segmentId: id, fwdSpeedLimit: parseInt(options.setSpeed), revSpeedLimit: parseInt(options.setSpeed) }); } // Handling the street if (options.setStreet) { let city; let street; city = getTopCity() || getEmptyCity(); street = wmeSDK.DataModel.Streets.getStreet({ cityId: city.id, streetName: '', }); log(`City ID: ${city?.id}, Street ID: ${street?.id}`); if(!street) { street = wmeSDK.DataModel.Streets.addStreet({ streetName: '', cityId: city.id }); log(`Created new empty street. Street ID: ${street?.id}`); } if (street) { 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 (non-compact mode)'); unpavedToggled = true; } } } catch (e) { log('Fallback to non-compact mode unpaved toggle method failed: ' + e); } } if (!unpavedToggled) { log('Could not toggle unpaved setting - no compatible elements found'); } } }) // Autosave - DELAYED AUTOSAVE if (options.autosave) { setTimeout(() => { log('Delayed Autosave starting...'); wmeSDK.Editing.save().then(() => { log('Delayed Autosave completed.'); }); }, 500); // 1000ms (1 second) delay before autosave } } const constructSettings = () => { const localOptions = getOptions(); const update = (key, value) => { const options = getOptions(); options[key] = value; localOptions[key] = value; 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' } ]; // Helper function to create checkboxes const createCheckbox = (option) => { const isChecked = localOptions[option.key]; const div = $(`
`); div.on('click', () => update(option.key, $(`#${option.id}`).prop('checked'))); return div; }; // -- Set up the tab for the script wmeSDK.Sidebar.registerScriptTab().then(({ tabLabel, tabPane }) => { tabLabel.innerText = 'EZRoads Mod'; tabLabel.title = 'Easily Update Roads'; // Setup base styles const styles = $(``); tabPane.innerHTML = '
'; const scriptContentPane = $('#ezroadsmod-settings'); scriptContentPane.append(styles); // Header section const header = $(`

EZRoads Mod

Current Version: ${ScriptVersion}
Update Keybind: g
`); scriptContentPane.append(header); // Road type section scriptContentPane.append(`
Set Road Type
`); const motorway = $(`

`); motorway.on('click', () => { update('roadType', 3); update('lockRank', 4); }); const major = $(`

`); major.on('click', () => { update('roadType', 6); update('lockRank', 3); }); const minor = $(`

`); minor.on('click', () => { update('roadType', 7); update('lockRank', 2); }); const primary = $(`

`); primary.on('click', () => { update('roadType', 2); update('lockRank', 1); }); const private = $(`

`); private.on('click', () => { update('roadType', 17); update('lockRank', 0); }); const parking = $(`

`) parking.on('click', () => { update('roadType', 20); update('lockRank', 0); }); const street = $(`

`) street.on('click', () => { update('roadType', 1); update('lockRank', 0); }); const offroad = $(`

`) offroad.on('click', () => { update('roadType', 8); update('lockRank', 0); }); const railroad = $(`

`) railroad.on('click', () => { update('roadType', 18); update('lockRank', 2); }) scriptContentPane.append(motorway).append(major).append(minor).append(primary).append(private).append(parking).append(street).append(offroad).append(railroad); scriptContentPane.append(`
Additional Options
`); // Additional options section const additionalSection = $(`
Additional Options
`); scriptContentPane.append(additionalSection); const additionalOptions = additionalSection.find('#additional-options'); checkboxOptions.forEach(option => { additionalOptions.append(createCheckbox(option)); }); // Speed setting section const speedInput = $(`
`); speedInput.find('input').on('change', function () { update('setSpeed', parseInt(this.value, 10)); }); scriptContentPane.append(speedInput); // Reset button section const resetButton = $(``); resetButton.on('click', function () { if (confirm('Are you sure you want to reset all options to default values? It will reload the webpage!')) { resetOptions(); } }); scriptContentPane.append(resetButton); }); };