Greasy Fork is available in English.
Easily update roads
当前为
// ==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);
});
};