Greasy Fork

Greasy Fork is available in English.

WME EZRoad Mod

Easily update roads

当前为 2025-03-09 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         WME EZRoad Mod
// @namespace    http://greasyfork.icu/users/1087400
// @version      0.2.3
// @description  Easily update roads
// @author       https://github.com/michaelrosstarr, http://greasyfork.icu/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
// ==/UserScript==

/*Script modified from WME EZRoad (http://greasyfork.icu/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 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;
    };

    // -- 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 = $(`<style>
            #ezroadsmod-settings h2, #ezroadsmod-settings h5 {
                margin-top: 0;
                margin-bottom: 10px;
            }
            .ezroadsmod-section {
                margin-bottom: 15px;
            }
            .ezroadsmod-option {
                margin-bottom: 5px;
            }
            .ezroadsmod-speed-input {
                margin-top: 10px;
            }
            .ezroadsmod-speed-input label {
                display: block;
                margin-bottom: 5px;
            }
            .ezroadsmod-speed-input input {
                width: 80px;
            }
            .ezroadsmod-reset-button {
                margin-top: 20px;
                padding: 8px 12px;
                background-color: #f44336;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-weight: bold;
            }
            .ezroadsmod-reset-button:hover {
                background-color: #d32f2f;
            }
        </style>`);
		
        tabPane.innerHTML = '<div id="ezroadsmod-settings"></div>';
        const scriptContentPane = $('#ezroadsmod-settings');
		scriptContentPane.append(styles);
		
		// Header section
        const header = $(`<div class="ezroadsmod-section">							 

        <h2>EZRoads Mod</h2>
            <div>Current Version: <b>${ScriptVersion}</b></div>
            <div>Update Keybind: <kbd>g</kbd></div>
        </div>`);
        scriptContentPane.append(header);
        // Road type section
        scriptContentPane.append(`<h5 style="margin-top: 0;">Set Road Type</h5>`);
        
		const motorway = $(`<div>
            <input type="radio" id="road-mwy" name="defaultRoad" ${localOptions.roadType === 3 && localOptions.lockRank === 4 && 'checked'}>
            <label for="road-mwy">Motorway</label><br>
        </div>`);
        motorway.on('click', () => { update('roadType', 3); update('lockRank', 4); });
        
		const major = $(`<div>
            <input type="radio" id="road-major" name="defaultRoad" ${localOptions.roadType === 6 && localOptions.lockRank === 3 && 'checked'}>
            <label for="road-major">Major Highway</label><br>
        </div>`);
        major.on('click', () => { update('roadType', 6); update('lockRank', 3); });

		const minor = $(`<div>
            <input type="radio" id="road-minor" name="defaultRoad" ${localOptions.roadType === 7 && localOptions.lockRank === 2 && 'checked'}>
            <label for="road-minor">Minor Highway</label><br>
        </div>`);
        minor.on('click', () => { update('roadType', 7); update('lockRank', 2); });

        const primary = $(`<div>
            <input type="radio" id="road-ps" name="defaultRoad" ${localOptions.roadType === 2 && localOptions.lockRank === 1 && 'checked'}>
            <label for="road-ps">Primary Street</label><br>
        </div>`);
        primary.on('click', () => { update('roadType', 2); update('lockRank', 1); });

        const private = $(`<div>
            <input type="radio" id="road-private" name="defaultRoad" ${localOptions.roadType === 17 && localOptions.lockRank === 0 &&  'checked'}>
            <label for="road-private">Private Road</label><br>
        </div>`);
        private.on('click', () => { update('roadType', 17); update('lockRank', 0); });

        const parking = $(`<div>
            <input type="radio" id="road-parking" name="defaultRoad" ${localOptions.roadType === 20 && localOptions.lockRank === 0 && 'checked'}>
            <label for="road-parking">Parking Lot Road</label><br>
        </div>`)
        parking.on('click', () => { update('roadType', 20); update('lockRank', 0); });

        const street = $(`<div>
            <input type="radio" id="road-street" name="defaultRoad" ${localOptions.roadType === 1 && localOptions.lockRank === 0 && 'checked'}>
            <label for="road-street">Street</label><br>
        </div>`)
        street.on('click', () => { update('roadType', 1); update('lockRank', 0); });

        const offroad = $(`<div>
            <input type="radio" id="offroad" name="defaultRoad" ${localOptions.roadType === 8 && localOptions.lockRank === 0 && 'checked'}>
            <label for="offroad">Set Offroad</label><br>
        </div>`)
        offroad.on('click', () => { update('roadType', 8); update('lockRank', 0); });

         const railroad = $(`<div>
            <input type="radio" id="railroad" name="defaultRoad" ${localOptions.roadType === 18 && localOptions.lockRank === 2 && 'checked'}>
            <label for="railroad">Set Railroad</label><br>
        </div>`)
        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(`<h5 style="margin-top: 0;">Additional Options</h5>`);

        // Additional options section
        const additionalSection = $(`<div class="ezroadsmod-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));
        });

        // Speed setting section
        const speedInput = $(`<div class="ezroadsmod-section ezroadsmod-speed-input">	
            <label for="setSpeed">Value to set speed to (set to -1 to disable)</label>
            <input type="number" id="setSpeed" name="setSpeed" value="${localOptions.setSpeed}">
        </div>`);		   
        speedInput.find('input').on('change', function () {
            update('setSpeed', parseInt(this.value, 10));
        });
        scriptContentPane.append(speedInput);

// Reset button section
        const resetButton = $(`<button class="ezroadsmod-reset-button">Reset All Options</button>`);
        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);
    });
};