Greasy Fork

Greasy Fork is available in English.

WME EZRoad Mod

Easily update roads

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

您需要先安装一款用户脚本管理器扩展,例如 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.2.1
// @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.
*******************************************************/
const ScriptName = GM_info.script.name;
const ScriptVersion = GM_info.script.version;
let wmeSDK;

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 = () => {
    return JSON.parse(window.localStorage.getItem('WME_EZRoads_Mod_Options')) || {roadType: 1, lockRank: 0, unpaved: false, setStreet: false, autosave: false, setSpeed: 40 };
	}

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 = () => {
    // Let's try to get the "None" street explicitly by name, if it exists.
    const city = getTopCity() || getEmptyCity();
    return wmeSDK.DataModel.Streets.getStreet({
        cityId: city.id,
        streetName: '', // "None" street has an empty name
    });
}


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 - RE-ENABLED with DELAY and DEBUG LOGGING
        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 - RE-ENABLED with DELAY and DEBUG LOGGING
		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 (remains same)
        if(options.setSpeed != -1) {
            wmeSDK.DataModel.Segments.updateSegment({
                segmentId: id,
                fwdSpeedLimit: parseInt(options.setSpeed),
                revSpeedLimit: parseInt(options.setSpeed)
            });
        }

        // Handling the street (remains same)
        if (options.setStreet) {
            let city;
            let street;
            city = getTopCity() || getEmptyCity();
            street = getEmptyStreet();
            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
                });
            } else {
                log("Error: Could not get or create empty street.");
            }
        }
    })

    // Autosave - DELAYED AUTOSAVE (remains same delay)
    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 = () => {

    let localOptions = getOptions();

    const update = (key, value) => {
        const options = getOptions();
        options[key] = value;
        localOptions[key] = value;
        saveOptions(options);
    }

    // -- Set up the tab for the script
    wmeSDK.Sidebar.registerScriptTab().then(({ tabLabel, tabPane }) => {
        tabLabel.innerText = 'EZRoads Mod';
        tabLabel.title = 'Easily Update Roads';

        tabPane.innerHTML = '<div id="ezroadsmod-settings"></div>';

        const scriptContentPane = $('#ezroadsmod-settings');

        scriptContentPane.append(`<h2 style="margin-top: 0;">EZRoads Mod</h2>`);
        scriptContentPane.append(`<span>Current Version: <b>${ScriptVersion}</b></span><br>`);
        scriptContentPane.append(`<span>Update Keybind: <kbd>g</kbd></span><br>`);

        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>`);

        const setStreet = $(`<div>
            <input type="checkbox" id="setStreet" name="setStreet"  ${localOptions.setStreet && 'checked'}>
            <label for="setStreet">Set Street To None</label><br/>
        </div>`)
        .on('click', () => update('setStreet', !localOptions.setStreet))


        const autosave = $(`<div>
            <input type="checkbox" id="autosave" name="autosave"  ${localOptions.autosave && 'checked'}>
            <label for="autosave">Autosave on Action</label><br/>
        </div>`)
        .on('click', () => update('autosave', !localOptions.autosave))

        const unpaved = $(`<div>
            <input type="checkbox" id="unpaved" name="unpaved"  ${localOptions.unpaved && 'checked'}>
            <label for="unpaved">Set Road as Unpaved</label>
        </div>`)
        .on('click', () => update('unpaved', !localOptions.unpaved))

        scriptContentPane.append(setStreet).append(autosave).append(unpaved);

        const speedInput = $(`
        <div>
            <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('blur', function () {
            update('setSpeed', this.value);
        });

        scriptContentPane.append(speedInput);

    });
}