您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Various UI changes to make editing faster and easier.
// ==UserScript== // @name WME POI Shortcuts // @namespace https://greasyfork.org/users/45389 // @version 2025.08.19.03 // @description Various UI changes to make editing faster and easier. // @author kid4rm90s // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/ // @license GNU GPLv3 // @connect greasyfork.org // @contributionURL https://github.com/WazeDev/Thank-The-Authors // @grant GM_xmlhttpRequest // @grant GM_addElement // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js // @require https://update.greasyfork.org/scripts/509664/WME%20Utils%20-%20Bootstrap.js // @require https://greasyfork.org/scripts/523706-google-link-enhancer/code/Link%20Enhancer.js // ==/UserScript== /* global WazeWrap */ /* global bootstrap */ https: (function () { ('use strict'); const updateMessage = '<br>Enhanced "Convert OTHER to Residential" functionality using shortcut key.</br><br>The shortcut now automatically copies venue names to house numbers and converts to residential places. </br><br>Compatibility with the New WME v2.309 with the types of House Numbers you can now map in WME Production for both Residential Point Places (RPP) and venues.</br>' const scriptName = GM_info.script.name; const scriptVersion = GM_info.script.version; const downloadUrl = 'https://greasyfork.org/scripts/545278-wme-poi-shortcuts/code/wme-poi-shortcuts.user.js'; const forumURL = 'https://greasyfork.org/scripts/545278-wme-poi-shortcuts/feedback'; // Gas Station Brand Names for Nepal and Pakistan const GAS_STATION_BRANDNAME = { Nepal: { countryCode: 'NP', brandnames: [ { primaryName: 'NOC', brand: 'Nepal Oil Corporation', website: 'noc.org.np', }, ], }, Pakistan: { countryCode: 'PK', brandnames: [ { primaryName: 'Askar 1', brand: 'Askar 1', aliases: ['Askar 1 Petrol Pump'], website: 'askaroil.com.pk', }, { primaryName: 'Attock', brand: 'Attock', aliases: ['Attock Petrol Pump'], website: 'apl.com.pk', }, { primaryName: 'Be Energy', brand: 'BE Energy', aliases: ['Be Petrol Pump'], website: 'beenergy.com.pk', }, { primaryName: 'Byco', brand: 'Byco', aliases: ['Byco Petrol Pump'], website: 'byco.com.pk', }, { primaryName: 'Caltex', brand: 'Caltex', aliases: ['Caltex Petrol Pump'], website: 'caltex.com', }, { primaryName: 'Go', brand: 'Go', aliases: ['Go Petrol Pump'], website: 'gno.com.pk', }, { primaryName: 'Hascol', brand: 'Hascol', aliases: [''], website: 'hascol.com', }, { primaryName: 'LaGuardia', brand: 'LaGuardia', aliases: ['LaGuardia'], website: 'laguardia-group.com', }, { primaryName: 'N3', brand: 'N3', aliases: ['N3 Petrol Pump'], website: 'n3.com.pk', }, { primaryName: 'PSO', brand: 'Pakistan State Oil', aliases: ['PSO Petrol Pump', 'Pakistan State Oil'], website: 'psopk.com', }, { primaryName: 'Puma Energy', brand: 'Puma', aliases: ['Puma'], website: 'pumaenergy.com', }, { primaryName: 'Shell', brand: 'Shell', aliases: ['Shell'], website: 'shell.com.pk', }, { primaryName: 'Taj Petroleum', brand: 'TAJ', aliases: ['Taj Petrol Pump'], website: 'tajcorporation.com', }, { primaryName: 'Total Parco', brand: 'TOTAL - PARCO', aliases: ['Total Parco', 'Total', 'Total Petrol Pump'], website: 'totalparco.com.pk', }, { primaryName: 'Zoom', brand: 'Zoom', aliases: ['Zoom Petroleum', 'Zoom Petrol Pump'], website: 'zoom.org.pk', }, { primaryName: 'Target', brand: null, aliases: ['Target Petrol Pump'], website: 'targetlubricants.com', }, ], }, }; const CHARGING_STATION_BRANDNAME = { Nepal: { countryCode: 'NP', brandnames: [ { primaryName: 'BYD', brand: 'BYD', aliases: ['EV Charging Station'], website: 'cimex.com.np/charging-stations', }, { primaryName: 'CG Motors', brand: 'CG Motors', aliases: ['EV Charging Station'], website: 'cg-ev.com/charger-station', }, { primaryName: 'MG Motors', brand: 'MG Motors', aliases: ['EV Charging Station'], website: 'mgmotors.com.np/locate-ev-charger', }, { primaryName: 'Tata Motors', brand: 'Tata Motors', aliases: ['EV Charging Station'], website: 'tatacars.sipradi.com.np/vehicle/charginglocation', }, { primaryName: 'Hyundai Motors', brand: 'Hyundai Motors', aliases: ['EV Charging Station'], website: 'laxmihyundai.com/charge-points', }, { primaryName: 'NEA', brand: 'Nepal Electricity Authority', aliases: ['EV Charging Station'], website: 'nea.org.np', }, { primaryName: 'ElectriVa', brand: 'ElectriVa Nepal', aliases: ['EV Charging Station'], website: 'electrivanepal.com/locations', }, { primaryName: 'Yatri', brand: 'Yatri', aliases: ['EV Charging Station'], website: 'yatrienergy.com/', }, { primaryName: 'three Go', brand: 'three Go', aliases: ['EV Charging Station'], website: 'www.theego.com.np/thee-go-chargepoint/', }, { primaryName: 'MAW Vriddhi', brand: 'MAW Vriddhi', aliases: ['EV Charging Station'], website: 'mawevcharging.com/', }, { primaryName: 'OmodaJaencoo', brand: 'OmodaJaencoo', aliases: ['EV Charging Station'], website: 'omodajaecoonepal.com/charging-stations-in-nepal', }, { primaryName: 'Charging Station', brand: '', aliases: ['EV Charging Station'], website: '', }, ], }, }; // Global variable to track the MutationObserver for aliases list let aliasListObserver = null; // Constants for timeouts and delays const ALIAS_INJECTION_DELAY = 50; const RETRY_INJECTION_DELAY = 100; const BRAND_BUTTON_RETRY_DELAY = 150; const SCRIPT_UPDATE_MONITOR_DELAY = 250; const UI_ELEMENT_WAIT_DELAY = 50; // Logging utility with consistent prefixes const Logger = { info: (message, ...args) => console.log(`[WME POI Shortcuts] ${message}`, ...args), warn: (message, ...args) => console.warn(`[WME POI Shortcuts] ${message}`, ...args), error: (message, ...args) => console.error(`[WME POI Shortcuts] ${message}`, ...args) }; // Helper function to safely disconnect observer function disconnectAliasObserver() { if (aliasListObserver) { try { aliasListObserver.disconnect(); aliasListObserver = null; } catch (error) { Logger.warn('Error disconnecting alias observer:', error); aliasListObserver = null; } } } if (typeof unsafeWindow !== 'undefined' && unsafeWindow.SDK_INITIALIZED) { unsafeWindow.SDK_INITIALIZED.then(initScript); } else if (typeof window.SDK_INITIALIZED !== 'undefined') { window.SDK_INITIALIZED.then(initScript); } else { Logger.error('WME SDK is not available. Script will not run.'); } // Inject custom CSS for grayed out disabled options injectCSSWithID('poiDisabledOptionStyle', `select[id^='poiItem'] option:disabled { color: #bbb !important; background: #000000ff !important; }`); // Inject CSS for swap names button injectCSSWithID( 'swapNamesButtonStyle', ` .alias-item-action-swap { margin-left: 4px !important; opacity: 1 !important; visibility: visible !important; } .alias-item-action-swap .w-icon-arrow-up { font-size: 14px !important; color: #ffffff !important; } .swap-names-container { text-align: center; } .swap-names-container .w-icon-arrow-up { margin-right: 4px; color: #ffffff !important; } ` ); // --- GLE (Google Link Enhancer) Integration --- // GLE settings and messages // Load GLE enabled state from localStorage let gleEnabled = false; let gleShowTempClosed = true; try { gleEnabled = JSON.parse(localStorage.getItem('wme-poi-shortcuts-gle-enabled')); } catch (e) { gleEnabled = false; } try { gleShowTempClosed = JSON.parse(localStorage.getItem('wme-poi-shortcuts-gle-show-temp-closed')); } catch (e) { gleShowTempClosed = true; } let GLE = { enabled: gleEnabled, showTempClosedPOIs: gleShowTempClosed, enable() { this.enabled = true; ToggleExternalProvidersCSS(true); }, disable() { this.enabled = false; ToggleExternalProvidersCSS(false); }, closedPlace: 'Google indicates this place is permanently closed.\nVerify with other sources or your editor community before deleting.', multiLinked: 'Linked more than once already. Please find and remove multiple links.', linkedToThisPlace: 'Already linked to this place', linkedNearby: 'Already linked to a nearby place', linkedToXPlaces: 'This is linked to {0} places', badLink: 'Invalid Google link. Please remove it.', tooFar: 'The Google linked place is more than {0} meters from the Waze place. Please verify the link is correct.', }; // Inject CSS helper function injectCSSWithID(id, css) { let style = document.getElementById(id); if (!style) { style = document.createElement('style'); style.id = id; style.type = 'text/css'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } } // Toggle external providers CSS function ToggleExternalProvidersCSS(truthiness) { if (truthiness) injectCSSWithID('poiExternalProvidersTweaks', '#edit-panel .external-providers-view .select2-container {width:90%; margin-bottom:2px;}'); else { var styles = document.getElementById('poiExternalProvidersTweaks'); if (styles) styles.parentNode.removeChild(styles); } } // Add GLE controls to the sidebar UI function buildGLEControls() { return ` <div style="margin:6px 0 10px 0; padding:4px 8px; background:transparent; border-radius:4px;"> <label style="font-size:10px; font-weight:bold;"> <input type="checkbox" id="_cbEnableGLE" ${GLE && GLE.enabled ? 'checked' : ''} /> Enable Google Link Enhancer </label> </div> `; } function initScript() { // initialize the sdk with your script id and script name const wmeSDK = typeof unsafeWindow !== 'undefined' && unsafeWindow.getWmeSdk ? unsafeWindow.getWmeSdk({ scriptId: 'wme-poi', scriptName: 'WME POI' }) : getWmeSdk({ scriptId: 'wme-poi', scriptName: 'WME POI' }); // Store the original GLE config const gleConfig = { enabled: GLE.enabled, showTempClosedPOIs: GLE.showTempClosedPOIs, closedPlace: GLE.closedPlace, multiLinked: GLE.multiLinked, linkedToThisPlace: GLE.linkedToThisPlace, linkedNearby: GLE.linkedNearby, linkedToXPlaces: GLE.linkedToXPlaces, badLink: GLE.badLink, tooFar: GLE.tooFar, }; GLE = new GoogleLinkEnhancer(); //***** Set Google Link Enhancer strings ***** GLE.strings.closedPlace = gleConfig.closedPlace; GLE.strings.multiLinked = gleConfig.multiLinked; GLE.strings.linkedToThisPlace = gleConfig.linkedToThisPlace; GLE.strings.linkedNearby = gleConfig.linkedNearby; GLE.strings.linkedToXPlaces = gleConfig.linkedToXPlaces; GLE.strings.badLink = gleConfig.badLink; GLE.strings.tooFar = gleConfig.tooFar; // Apply the config to the GoogleLinkEnhancer instance AFTER strings are set GLE.showTempClosedPOIs = gleConfig.showTempClosedPOIs; if (gleConfig.enabled) { GLE.enable(); } // query the WME data model // Example: Get the currently selected segment if available const selection = wmeSDK.Editing.getSelection(); let mySegment; if (selection && selection.objectType === 'segment' && selection.ids && selection.ids.length === 1) { mySegment = wmeSDK.DataModel.Segments.getById({ segmentId: selection.ids[0] }); if (mySegment && mySegment.isAtoB) { // do something } } // register to events wmeSDK.Events.once({ eventName: 'wme-ready' }).then(() => { // Setup custom shortcuts after WME is ready setupShortcuts(wmeSDK); // Register script sidebar tab for venue dropdown registerSidebarScriptTab(wmeSDK); // Check for initial venue selection and inject swap button if needed setTimeout(() => { injectButtonStation(wmeSDK); injectSwapNamesButton(wmeSDK); }, 500); // Small delay to ensure UI is fully loaded }); wmeSDK.Events.on({ eventName: 'wme-map-move', eventHandler: () => { /* Handle map move events */ }, }); wmeSDK.Events.on({ eventName: 'wme-map-data-loaded', eventHandler: () => { /* Handle map data loaded events */ }, }); wmeSDK.Events.on({ eventName: 'wme-selection-changed', eventHandler: () => { injectButtonStation(wmeSDK); injectSwapNamesButton(wmeSDK); }, }); } // --- Persistence Helpers --- function getPOIShortcutsConfig() { try { return JSON.parse(localStorage.getItem('wme-poi-shortcuts-config') || '{}'); } catch (e) { return {}; } } function setPOIShortcutsConfig(config) { localStorage.setItem('wme-poi-shortcuts-config', JSON.stringify(config)); } function savePOIShortcutItem(itemNumber) { const config = getPOIShortcutsConfig(); config[itemNumber] = { category: $(`#poiItem${itemNumber}`).val(), lock: $(`#poiLock${itemNumber}`).val(), geometry: $(`#poiGeom${itemNumber}`).val(), }; setPOIShortcutsConfig(config); } function loadPOIShortcutItem(itemNumber) { const config = getPOIShortcutsConfig(); if (config[itemNumber]) { $(`#poiItem${itemNumber}`).val(config[itemNumber].category); $(`#poiLock${itemNumber}`).val(config[itemNumber].lock); $(`#poiGeom${itemNumber}`).val(config[itemNumber].geometry); } } // --- UI Builders --- function buildItemList(itemNumber) { // Categories and subcategories as per latest WME spec const VENUE_CATEGORIES = [ { key: 'CAR_SERVICES', icon: 'car-services', subs: ['CAR_WASH', 'CHARGING_STATION', 'GARAGE_AUTOMOTIVE_SHOP', 'GAS_STATION'] }, { key: 'CRISIS_LOCATIONS', icon: 'crisis-locations', subs: ['DONATION_CENTERS', 'SHELTER_LOCATIONS'] }, { key: 'CULTURE_AND_ENTERTAINEMENT', icon: 'culture-and-entertainement', subs: ['ART_GALLERY', 'CASINO', 'CLUB', 'TOURIST_ATTRACTION_HISTORIC_SITE', 'MOVIE_THEATER', 'MUSEUM', 'MUSIC_VENUE', 'PERFORMING_ARTS_VENUE', 'GAME_CLUB', 'STADIUM_ARENA', 'THEME_PARK', 'ZOO_AQUARIUM', 'RACING_TRACK', 'THEATER'], }, { key: 'FOOD_AND_DRINK', icon: 'food-and-drink', subs: ['RESTAURANT', 'BAKERY', 'DESSERT', 'CAFE', 'FAST_FOOD', 'FOOD_COURT', 'BAR', 'ICE_CREAM'] }, { key: 'LODGING', icon: 'lodging', subs: ['HOTEL', 'HOSTEL', 'CAMPING_TRAILER_PARK', 'COTTAGE_CABIN', 'BED_AND_BREAKFAST'] }, { key: 'NATURAL_FEATURES', icon: 'natural-features', subs: ['ISLAND', 'SEA_LAKE_POOL', 'RIVER_STREAM', 'FOREST_GROVE', 'FARM', 'CANAL', 'SWAMP_MARSH', 'DAM'] }, { key: 'OTHER', icon: 'other', subs: ['CONSTRUCTION_SITE'] }, { key: 'OUTDOORS', icon: 'outdoors', subs: ['PARK', 'PLAYGROUND', 'BEACH', 'SPORTS_COURT', 'GOLF_COURSE', 'PLAZA', 'PROMENADE', 'POOL', 'SCENIC_LOOKOUT_VIEWPOINT', 'SKI_AREA'] }, { key: 'PARKING_LOT', icon: 'parking-lot', subs: [] }, { key: 'PROFESSIONAL_AND_PUBLIC', icon: 'professional-and-public', subs: [ 'COLLEGE_UNIVERSITY', 'SCHOOL', 'CONVENTIONS_EVENT_CENTER', 'GOVERNMENT', 'LIBRARY', 'CITY_HALL', 'ORGANIZATION_OR_ASSOCIATION', 'PRISON_CORRECTIONAL_FACILITY', 'COURTHOUSE', 'CEMETERY', 'FIRE_DEPARTMENT', 'POLICE_STATION', 'MILITARY', 'HOSPITAL_URGENT_CARE', 'DOCTOR_CLINIC', 'OFFICES', 'POST_OFFICE', 'RELIGIOUS_CENTER', 'KINDERGARDEN', 'FACTORY_INDUSTRIAL', 'EMBASSY_CONSULATE', 'INFORMATION_POINT', 'EMERGENCY_SHELTER', 'TRASH_AND_RECYCLING_FACILITIES', ], }, { key: 'SHOPPING_AND_SERVICES', icon: 'shopping-and-services', subs: [ 'ARTS_AND_CRAFTS', 'BANK_FINANCIAL', 'SPORTING_GOODS', 'BOOKSTORE', 'PHOTOGRAPHY', 'CAR_DEALERSHIP', 'FASHION_AND_CLOTHING', 'CONVENIENCE_STORE', 'PERSONAL_CARE', 'DEPARTMENT_STORE', 'PHARMACY', 'ELECTRONICS', 'FLOWERS', 'FURNITURE_HOME_STORE', 'GIFTS', 'GYM_FITNESS', 'SWIMMING_POOL', 'HARDWARE_STORE', 'MARKET', 'SUPERMARKET_GROCERY', 'JEWELRY', 'LAUNDRY_DRY_CLEAN', 'SHOPPING_CENTER', 'MUSIC_STORE', 'PET_STORE_VETERINARIAN_SERVICES', 'TOY_STORE', 'TRAVEL_AGENCY', 'ATM', 'CURRENCY_EXCHANGE', 'CAR_RENTAL', 'TELECOM', ], }, { key: 'TRANSPORTATION', icon: 'transportation', subs: ['AIRPORT', 'BUS_STATION', 'FERRY_PIER', 'SEAPORT_MARINA_HARBOR', 'SUBWAY_STATION', 'TRAIN_STATION', 'BRIDGE', 'TUNNEL', 'TAXI_STATION', 'JUNCTION_INTERCHANGE', 'REST_AREAS', 'CARPOOL_SPOT'], }, ]; let html = `<select id="poiItem${itemNumber}" style="font-size:10px;height:20px;width:100%;max-width:200px;margin:2px 0;">`; VENUE_CATEGORIES.forEach((cat) => { try { const categoryName = I18n?.translations?.[I18n.currentLocale()]?.venues?.categories?.[cat.key] || cat.key; html += `<option value="${cat.key}" data-icon="${cat.icon}" style="font-weight:bold;">${categoryName}</option>`; cat.subs.forEach((sub) => { const subCategoryName = I18n?.translations?.[I18n.currentLocale()]?.venues?.categories?.[sub] || sub; html += `<option value="${sub}" data-icon="${cat.icon}">${subCategoryName}</option>`; }); } catch (e) { // Fallback if I18n is not available html += `<option value="${cat.key}" data-icon="${cat.icon}" style="font-weight:bold;">${cat.key}</option>`; cat.subs.forEach((sub) => { html += `<option value="${sub}" data-icon="${cat.icon}">${sub}</option>`; }); } }); html += '</select>'; return html; } function buildLockLevelDropdown(itemNumber) { // Show lock dropdown for all 10 items let html = `<select id="poiLock${itemNumber}" style="margin-left:4px;font-size:10px;height:20px;width:35px;">`; for (let i = 0; i <= 4; i++) { html += `<option value="${i}">${i + 1}</option>`; } html += '</select>'; return html; } function buildGeometryTypeDropdown(itemNumber) { // Dropdown for geometry type: Point or Area return `<select id="poiGeom${itemNumber}" style="margin-left:4px;font-size:10px;height:20px;width:55px;"> <option value="area">Area</option> <option value="point">Point</option> </select>`; } function buildItemOption(itemNumber) { var $section = $('<div>', { style: 'padding:4px 8px;font-size:10px;', id: 'poiPlaceCat' + itemNumber }); $section.html( [ `<span style="font-size:10px;font-weight:bold;">Item ${itemNumber}</span>`, buildItemList(itemNumber), `<div style="display:flex;align-items:center;gap:6px;margin:3px 0 0 0;"> <label style="font-size:10px;min-width:28px;">Lock</label> ${buildLockLevelDropdown(itemNumber)} <label style="font-size:10px;min-width:40px;">Geometry</label> ${buildGeometryTypeDropdown(itemNumber)} </div>`, ].join(' ') ); return $section.html(); } function buildAllItemOptions() { let html = ''; for (let i = 1; i <= 10; i++) { html += buildItemOption(i); } html += `<div style='font-size:10px;color:#888;margin-top:8px;'>You can bind keyboard shortcuts using WME's native shortcuts section.</div>`; setTimeout(() => { for (let i = 1; i <= 10; i++) { loadPOIShortcutItem(i); //legacy shortcuts key added from here // Populate shortcut input with the actual shortcut key const shortcutKey = i === 10 ? 'Ctrl+0' : `Ctrl+${i}`; $(`#poiShortcut${i}`).val(shortcutKey); // legacy shortcuts key added until above // Save on change $(`#poiItem${i},#poiLock${i},#poiGeom${i}`) .off('change.wmepoi') .on('change.wmepoi', function () { savePOIShortcutItem(i); // Prevent duplicate category selection // if (this.id.startsWith('poiItem')) { // const selectedCategories = []; // for (let j = 1; j <= 10; j++) { // const val = $(`#poiItem${j}`).val(); // if (val) selectedCategories.push(val); // } // for (let j = 1; j <= 10; j++) { // $(`#poiItem${j} option`).prop('disabled', false).removeAttr('title'); // } // for (let j = 1; j <= 10; j++) { // const currentVal = $(`#poiItem${j}`).val(); // for (const cat of selectedCategories) { // if (cat !== currentVal) { // $(`#poiItem${j} option[value='${cat}']`).prop('disabled', true).attr('title', 'this category is already selected.'); // } // } // } // } }); } // Initial duplicate prevention // const selectedCategories = []; // for (let j = 1; j <= 10; j++) { // const val = $(`#poiItem${j}`).val(); // if (val) selectedCategories.push(val); // } // for (let j = 1; j <= 10; j++) { // $(`#poiItem${j} option`).prop('disabled', false).removeAttr('title'); // } // for (let j = 1; j <= 10; j++) { // const currentVal = $(`#poiItem${j}`).val(); // for (const cat of selectedCategories) { // if (cat !== currentVal) { // $(`#poiItem${j} option[value='${cat}']`).prop('disabled', true).attr('title', 'this category is already selected.'); // } // } // } }, 0); return html; } /* // --- wmeSDK Shortcuts Setup --- // TODO: Re-enable when wmeSDK fixes shortcuts persistence after page refresh /* function setupShortcuts(wmeSDK) { // Create 10 POI shortcut actions, one for each item for (let i = 1; i <= 10; i++) { // Assign shortcutKeys: C1-C9, C0 for 10 const shortcutKey = i === 10 ? 'C0' : `C${i}`; const shortcutId = `create-poi-shortcut-${i}`; // Remove previous shortcut if registered if (wmeSDK.Shortcuts.isShortcutRegistered({ shortcutId })) { wmeSDK.Shortcuts.deleteShortcut({ shortcutId }); } // Check if shortcut keys are in use if (wmeSDK.Shortcuts.areShortcutKeysInUse({ shortcutKeys: shortcutKey })) { Logger.warn(`Shortcut keys ${shortcutKey} already in use, skipping registration for POI Shortcut #${i}`); continue; } wmeSDK.Shortcuts.createShortcut({ callback: () => { // Get selected values from the UI for this item const cat = $(`#poiItem${i}`).val(); const lock = parseInt($(`#poiLock${i}`).val(), 10); const geomType = $(`#poiGeom${i}`).val(); // Geometry: area = drawPolygon, point = drawPoint let drawPromise = geomType === 'point' ? wmeSDK.Map.drawPoint() : wmeSDK.Map.drawPolygon(); drawPromise.then((geometry) => { let newVenue = wmeSDK.DataModel.Venues.addVenue({ category: cat, geometry: geometry, }); wmeSDK.Editing.setSelection({ selection: { ids: [newVenue.toString()], objectType: 'venue', }, }); // Only set lock if lock > 0 (lockRank 1-4) if (!isNaN(lock) && lock > 0) { wmeSDK.DataModel.Venues.updateVenue({ venueId: newVenue.toString(), lockRank: lock, }); } // Nepal-specific logic for Gas Station const topCountry = wmeSDK.DataModel.Countries.getTopCountry(); if (topCountry && (topCountry.name === 'Nepal' || topCountry.code === 'NP') && cat === 'GAS_STATION') { wmeSDK.DataModel.Venues.updateVenue({ venueId: newVenue.toString(), name: 'NOC', brand: 'Nepal Oil Corporation', }); } }); }, description: `Create POI Shortcut #${i}`, shortcutId, shortcutKeys: shortcutKey, }); } // Shortcuts that click on WME's existing UI buttons for POI creation/modification wmeSDK.Shortcuts.createShortcut({ callback: () => { $("wz-icon[name='toll-booth']").parent().trigger('click'); }, description: 'Add Toll Booth', shortcutId: 'add-toll-booth', shortcutKeys: null, }); wmeSDK.Shortcuts.createShortcut({ callback: () => { $("wz-icon[name='railway-crossing']").parent().trigger('click'); }, description: 'Add Level Crossing', shortcutId: 'add-level-crossing', shortcutKeys: null, }); wmeSDK.Shortcuts.createShortcut({ callback: () => { $("wz-icon[name='school-zone']").parent().trigger('click'); }, description: 'Create School Zone', shortcutId: 'create-school-zone', shortcutKeys: null, }); } */ /***********************************************legacy shortcuts below*********************************************** */ // --- Legacy Shortcuts Setup (Temporary until wmeSDK fixes shortcuts persistence) --- function setupShortcuts(wmeSDK) { // Legacy shortcuts configuration - maps shortcut numbers to keyboard combos var shortcutsConfig = [ { handler: 'WME-POI-Shortcuts_poi1', title: 'POI Shortcut 1', func: function (ev) { createPOIFromShortcut(1, wmeSDK); }, key: null, arg: { slotNumber: 1 }, }, { handler: 'WME-POI-Shortcuts_poi2', title: 'POI Shortcut 2', func: function (ev) { createPOIFromShortcut(2, wmeSDK); }, key: null, arg: { slotNumber: 2 }, }, { handler: 'WME-POI-Shortcuts_poi3', title: 'POI Shortcut 3', func: function (ev) { createPOIFromShortcut(3, wmeSDK); }, key: null, arg: { slotNumber: 3 }, }, { handler: 'WME-POI-Shortcuts_poi4', title: 'POI Shortcut 4', func: function (ev) { createPOIFromShortcut(4, wmeSDK); }, key: null, arg: { slotNumber: 4 }, }, { handler: 'WME-POI-Shortcuts_poi5', title: 'POI Shortcut 5', func: function (ev) { createPOIFromShortcut(5, wmeSDK); }, key: null, arg: { slotNumber: 5 }, }, { handler: 'WME-POI-Shortcuts_poi6', title: 'POI Shortcut 6', func: function (ev) { createPOIFromShortcut(6, wmeSDK); }, key: null, arg: { slotNumber: 6 }, }, { handler: 'WME-POI-Shortcuts_poi7', title: 'POI Shortcut 7', func: function (ev) { createPOIFromShortcut(7, wmeSDK); }, key: null, arg: { slotNumber: 7 }, }, { handler: 'WME-POI-Shortcuts_poi8', title: 'POI Shortcut 8', func: function (ev) { createPOIFromShortcut(8, wmeSDK); }, key: null, arg: { slotNumber: 8 }, }, { handler: 'WME-POI-Shortcuts_poi9', title: 'POI Shortcut 9', func: function (ev) { createPOIFromShortcut(9, wmeSDK); }, key: null, arg: { slotNumber: 9 }, }, { handler: 'WME-POI-Shortcuts_poi10', title: 'POI Shortcut 10', func: function (ev) { createPOIFromShortcut(10, wmeSDK); }, key: null, arg: { slotNumber: 10 }, }, { handler: 'WME-POI-Shortcuts_toll-booth', title: 'Add Toll Booth', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_toll_booth', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Toll Booth</b>`, false, false, 2000); $("wz-icon[name='toll-booth']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_level-crossing', title: 'Add Level Crossing', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_railroad_crossing', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Level Crossing</b>`, false, false, 2000); $("wz-icon[name='railway-crossing']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_school-zone', title: 'Create School Zone', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_school_zone', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>School Zone</b>`, false, false, 2000); $("wz-icon[name='school-zone']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_sharp-curves', title: 'Create Sharp Curves', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_dangerous_curve', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Sharp Curves</b>`, false, false, 2000); $("wz-icon[name='sharp-curve-ahead']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_complex-junctions', title: 'Create Complex Junctions', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_dangerous_intersection', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Complex Junctions</b>`, false, false, 2000); $("wz-icon[name='dangerous-intersection']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_multiple-lanes-merging', title: 'Create Multiple Lanes Merging', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_dangerous_merge', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Multiple Lanes Merging</b>`, false, false, 2000); $("wz-icon[name='merge-ahead']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_convert-other-to-residential', title: 'Convert OTHER to Residential (Copy Name to House Number)', func: function (ev) { convertOtherToResidential(wmeSDK); }, key: -1, // No default key, user can set custom shortcut in WME settings arg: {}, }, ]; // Register legacy shortcuts for (var i = 0; i < shortcutsConfig.length; ++i) { WMEKSRegisterKeyboardShortcut('WME-POI-Shortcuts', 'WME POI Shortcuts', shortcutsConfig[i].handler, shortcutsConfig[i].title, shortcutsConfig[i].func, shortcutsConfig[i].key, shortcutsConfig[i].arg); } WMEKSLoadKeyboardShortcuts('WME-POI-Shortcuts'); window.addEventListener( 'beforeunload', function () { WMEKSSaveKeyboardShortcuts('WME-POI-Shortcuts'); }, false ); } /** * Converts OTHER type venues to residential places by copying the primary name * to the venue address house number and triggering the conversion button. * * @param {Object} wmeSDK - The WME SDK instance */ function convertOtherToResidential(wmeSDK) { try { const venue = getSelectedVenue(wmeSDK); if (!venue) return; if (!isValidOtherVenue(venue)) return; if (!hasValidPrimaryName(venue)) return; checkAndUpdateVenueAddress(wmeSDK, venue); } catch (error) { Logger.error('Error in convertOtherToResidential:', error); WazeWrap.Alerts.error('POI Shortcut', 'An unexpected error occurred during conversion.', false, false, 3000); } } /** * Gets the currently selected venue from WME * * @param {Object} wmeSDK - The WME SDK instance * @returns {Object|null} - The selected venue or null if invalid selection */ function getSelectedVenue(wmeSDK) { const selection = wmeSDK.Editing.getSelection(); if (!selection || selection.objectType !== 'venue' || !selection.ids || selection.ids.length !== 1) { WazeWrap.Alerts.warning('POI Shortcut', 'Please select a venue first.', false, false, 3000); return null; } const venueId = selection.ids[0]; const venue = wmeSDK.DataModel.Venues.getById({ venueId }); if (!venue) { WazeWrap.Alerts.error('POI Shortcut', 'Venue not found.', false, false, 3000); return null; } return venue; } /** * Validates if the venue is of type OTHER * * @param {Object} venue - The venue object * @returns {boolean} - True if venue is valid OTHER type */ function isValidOtherVenue(venue) { const otherCategories = ['OTHER', 'other']; const venueCategories = venue.categories || []; const isOther = venueCategories.some(cat => otherCategories.includes(cat)); console.log('Venue categories:', venueCategories); if (!isOther) { WazeWrap.Alerts.warning('POI Shortcut', `This function only works with venues of type OTHER. Actual: ${venueCategories.join(', ')}`, false, false, 3000); return false; } return true; } /** * Validates if the venue has a valid primary name * * @param {Object} venue - The venue object * @returns {boolean} - True if venue has valid primary name */ function hasValidPrimaryName(venue) { if (!venue.name || !venue.name.trim()) { WazeWrap.Alerts.warning('POI Shortcut', 'Primary name is empty and cannot be used as house number.', false, false, 3000); return false; } return true; } /** * Checks venue address and updates it if needed * * @param {Object} wmeSDK - The WME SDK instance * @param {Object} venue - The venue object */ function checkAndUpdateVenueAddress(wmeSDK, venue) { checkExistingHouseNumber(wmeSDK, venue) .then(hasHouseNumber => { if (hasHouseNumber) { WazeWrap.Alerts.warning('POI Shortcut', 'Venue already has a house number in its address.', false, false, 3000); return; } return updateVenueAddressHouseNumber(wmeSDK, venue); }) .then(() => { triggerResidentialConversion(venue.name); }) .catch(error => { Logger.error('Error updating venue address:', error); WazeWrap.Alerts.error('POI Shortcut', 'Failed to update venue address.', false, false, 3000); }); } /** * Checks if venue already has a house number in its address * * @param {Object} wmeSDK - The WME SDK instance * @param {Object} venue - The venue object * @returns {Promise<boolean>} - Promise resolving to true if house number exists */ function checkExistingHouseNumber(wmeSDK, venue) { return new Promise((resolve, reject) => { try { const address = wmeSDK.DataModel.Venues.getAddress({ venueId: venue.id }); const hasExistingHouseNumber = address && address.houseNumber && address.houseNumber.trim(); resolve(!!hasExistingHouseNumber); } catch (error) { reject(error); } }); } /** * Updates venue address with the primary name as house number * * @param {Object} wmeSDK - The WME SDK instance * @param {Object} venue - The venue object * @returns {Promise<void>} - Promise that resolves when update is complete */ function updateVenueAddressHouseNumber(wmeSDK, venue) { return new Promise((resolve, reject) => { try { wmeSDK.DataModel.Venues.updateAddress({ venueId: venue.id, houseNumber: venue.name }); resolve(); } catch (error) { reject(error); } }); } /** * Triggers the residential conversion by clicking the convert button * * @param {string} venueName - The name of the venue being converted */ function triggerResidentialConversion(venueName) { // Small delay to ensure UI updates after address change setTimeout(() => { const buttonClicked = clickConvertToResidentialButton(); if (buttonClicked) { WazeWrap.Alerts.success('POI Shortcut', `Successfully converted venue "${venueName}" to residential.`, false, false, 4000); } else { WazeWrap.Alerts.warning('POI Shortcut', `House number set to "${venueName}". Please manually click "Convert to residential" button.`, false, false, 4000); } }, 500); } /** * Attempts to click the convert to residential button * * @returns {boolean} - True if button was found and clicked */ function clickConvertToResidentialButton() { const selectors = [ 'wz-button.toggle-residential-button[color="secondary"][size="sm"]', 'wz-button.toggle-residential-button', '.toggle-residential-control wz-button' ]; for (const selector of selectors) { const button = document.querySelector(selector); if (button && button.getAttribute('disabled') !== 'true') { button.click(); Logger.info(`Clicked convert to residential button using selector: ${selector}`); return true; } } Logger.warn('Convert to residential button not found or disabled'); return false; } // Function to create POI from shortcut slot function createPOIFromShortcut(slotNumber, wmeSDK) { try { // Get selected values from the UI for this item const cat = $(`#poiItem${slotNumber}`).val(); const lock = parseInt($(`#poiLock${slotNumber}`).val(), 10); const geomType = $(`#poiGeom${slotNumber}`).val(); if (!cat || cat === '') { Logger.warn(`POI Shortcut ${slotNumber}: No category selected`); return; } // Show WazeWrap alert with POI info before drawing const poiName = $(`#poiItem${slotNumber} option:selected`).text(); const lockLevel = !isNaN(lock) ? parseInt(lock, 10) + 1 : 1; const areaType = geomType === 'point' ? 'Point' : 'Area'; WazeWrap.Alerts.info('POI Shortcut', `Selected POI Name: <b>${poiName}</b><br>Lock Level: <b>${lockLevel}</b><br>Type: <b>${areaType}</b>`, false, false, 2500); // Geometry: area = drawPolygon, point = drawPoint let drawPromise = geomType === 'point' ? wmeSDK.Map.drawPoint() : wmeSDK.Map.drawPolygon(); drawPromise .then((geometry) => { let newVenue = wmeSDK.DataModel.Venues.addVenue({ category: cat, geometry: geometry, }); // Add a small delay to ensure the venue is fully created before selecting it setTimeout(() => { wmeSDK.Editing.setSelection({ selection: { ids: [newVenue.toString()], objectType: 'venue', }, }); }, 100); // Only set lock if lock > 0 (lockRank 1-4) if (!isNaN(lock) && lock > 0) { setTimeout(() => { wmeSDK.DataModel.Venues.updateVenue({ venueId: newVenue.toString(), lockRank: lock, }); }, 200); } // Nepal-specific logic for Gas Station const topCountry = wmeSDK.DataModel.Countries.getTopCountry(); if (topCountry && (topCountry.name === 'Nepal' || topCountry.code === 'NP') && cat === 'GAS_STATION') { wmeSDK.DataModel.Venues.updateVenue({ venueId: newVenue.toString(), name: 'NOC', brand: 'Nepal Oil Corporation', }); } }) .catch((err) => { if (err && err.name === 'InvalidStateError') { Logger.info('POI drawing was cancelled by the user.'); } else { Logger.error('Error during POI drawing:', err); } }); } catch (error) { Logger.error(`Error creating POI from shortcut ${slotNumber}:`, error); } } // Helper function to ensure hazard layer group and specific hazard layer are enabled function ensureHazardLayersEnabled(hazardLayerId, callback) { try { // Wait a bit to ensure the layer UI is ready setTimeout(() => { // First, ensure the permanent hazards group is enabled const hazardGroupToggle = document.getElementById('layer-switcher-group_permanent_hazards'); if (hazardGroupToggle) { // For wz-toggle-switch: checked="" means enabled, checked="false" means disabled const checkedAttr = hazardGroupToggle.getAttribute('checked'); const isGroupEnabled = checkedAttr === '' || checkedAttr === 'true'; if (!isGroupEnabled) { hazardGroupToggle.click(); // Wait for the group to be enabled before enabling individual layers setTimeout(() => { enableSpecificHazardLayer(hazardLayerId, callback); }, 400); return; } } else { Logger.warn('Hazard group toggle not found'); } // If group is already enabled, directly enable the specific layer enableSpecificHazardLayer(hazardLayerId, callback); }, UI_ELEMENT_WAIT_DELAY); } catch (error) { Logger.error('Error enabling hazard layers:', error); // Execute callback even if there's an error to prevent hanging if (callback && typeof callback === 'function') { setTimeout(callback, RETRY_INJECTION_DELAY); } } } // Helper function to enable a specific hazard layer function enableSpecificHazardLayer(hazardLayerId, callback) { try { if (hazardLayerId) { const hazardLayerCheckbox = document.getElementById(hazardLayerId); if (hazardLayerCheckbox) { // For wz-checkbox: checked="" means enabled, checked="false" means disabled const checkedAttr = hazardLayerCheckbox.getAttribute('checked'); const isLayerEnabled = checkedAttr === '' || checkedAttr === 'true'; if (!isLayerEnabled) { hazardLayerCheckbox.click(); // Wait for layer to be enabled before executing callback setTimeout(() => { if (callback && typeof callback === 'function') { callback(); } }, 300); } else { // Layer is already enabled, execute callback immediately if (callback && typeof callback === 'function') { callback(); } } } else { Logger.warn(`Hazard layer element not found: ${hazardLayerId}`); // Execute callback even if element not found to prevent hanging if (callback && typeof callback === 'function') { setTimeout(callback, 100); } } } else { // No specific layer ID provided, execute callback if (callback && typeof callback === 'function') { callback(); } } } catch (error) { Logger.error('Error enabling specific hazard layer:', error); // Execute callback even if there's an error to prevent hanging if (callback && typeof callback === 'function') { setTimeout(callback, RETRY_INJECTION_DELAY); } } } // --- Legacy Keyboard Shortcuts System (from WME Street to River PLUS) --- function WMEKSRegisterKeyboardShortcut(scriptName, shortcutsHeader, newShortcut, shortcutDescription, functionToCall, shortcutKeysObj, arg) { try { I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName].members.length; } catch (c) { (W.accelerators.Groups[scriptName] = []), (W.accelerators.Groups[scriptName].members = []), (I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName] = []), (I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName].description = shortcutsHeader), (I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName].members = []); } if (functionToCall && 'function' == typeof functionToCall) { (I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName].members[newShortcut] = shortcutDescription), W.accelerators.addAction(newShortcut, { group: scriptName, }); var i = '-1', j = {}; (j[i] = newShortcut), W.accelerators._registerShortcuts(j), null !== shortcutKeysObj && ((j = {}), (j[shortcutKeysObj] = newShortcut), W.accelerators._registerShortcuts(j)), W.accelerators.events.register(newShortcut, null, function () { functionToCall(arg); }); } else alert('The function ' + functionToCall + ' has not been declared'); } function WMEKSLoadKeyboardShortcuts(scriptName) { Logger.info(`Loading keyboard shortcuts for ${scriptName}`); if (localStorage[scriptName + 'KBS']) { const shortcuts = JSON.parse(localStorage[scriptName + 'KBS']); for (let i = 0; i < shortcuts.length; i++) { try { W.accelerators._registerShortcuts(shortcuts[i]); } catch (error) { Logger.error('Error registering shortcut:', error); } } } } function WMEKSSaveKeyboardShortcuts(scriptName) { Logger.info(`Saving keyboard shortcuts for ${scriptName}`); const shortcuts = []; for (var actionName in W.accelerators.Actions) { var shortcutString = ''; if (W.accelerators.Actions[actionName].group == scriptName) { W.accelerators.Actions[actionName].shortcut ? (W.accelerators.Actions[actionName].shortcut.altKey === !0 && (shortcutString += 'A'), W.accelerators.Actions[actionName].shortcut.shiftKey === !0 && (shortcutString += 'S'), W.accelerators.Actions[actionName].shortcut.ctrlKey === !0 && (shortcutString += 'C'), '' !== shortcutString && (shortcutString += '+'), W.accelerators.Actions[actionName].shortcut.keyCode && (shortcutString += W.accelerators.Actions[actionName].shortcut.keyCode)) : (shortcutString = '-1'); var shortcutObj = {}; (shortcutObj[shortcutString] = W.accelerators.Actions[actionName].id), (shortcuts[shortcuts.length] = shortcutObj); } } localStorage[scriptName + 'KBS'] = JSON.stringify(shortcuts); } /******************************************legacy shortcuts until here above************************************ */ function getGasStationCategoryKey() { // Use I18n to get the correct category key for gas station // Fallback to 'GAS_STATION' if not found let locale = typeof I18n !== 'undefined' && I18n.currentLocale ? I18n.currentLocale() : 'en'; let categories = I18n?.translations?.[locale]?.venues?.categories || {}; // Find the key for 'Gas Station' or 'Petrol Station' in the current language for (const key in categories) { if (categories[key] === 'Gas Station' || categories[key] === 'Petrol Station') { return key; } } // Fallback to 'GAS_STATION' return 'GAS_STATION'; } function getChargingStationCategoryKey() { // Charging station category key is consistent across all locales return 'CHARGING_STATION'; } function swapPrimaryAndAliasNames(wmeSDK, aliasIndex = 0) { // Only run if a venue is selected const selection = wmeSDK.Editing.getSelection(); if (!selection || selection.objectType !== 'venue' || !selection.ids || selection.ids.length !== 1) { Logger.warn('No venue selected for name swapping'); return; } const venueId = selection.ids[0]; const venue = wmeSDK.DataModel.Venues.getById({ venueId }); if (!venue) { Logger.warn('Venue not found'); return; } // Check if venue has a name and at least one alias if (!venue.name || !venue.aliases || venue.aliases.length === 0) { Logger.warn('Venue must have both a primary name and at least one alias to swap'); return; } // Validate alias index if (aliasIndex < 0 || aliasIndex >= venue.aliases.length) { Logger.warn(`Invalid alias index: ${aliasIndex}. Available aliases: ${venue.aliases.length}`); return; } // Get current primary name and target alias const currentPrimaryName = venue.name; const targetAlias = venue.aliases[aliasIndex]; // Create new aliases array with the old primary name replacing the target alias const newAliases = [...venue.aliases]; newAliases[aliasIndex] = currentPrimaryName; try { // Update venue with swapped names wmeSDK.DataModel.Venues.updateVenue({ venueId: venueId, name: targetAlias, aliases: newAliases, }); Logger.info(`Swapped names: "${currentPrimaryName}" ↔ "${targetAlias}" (alias index: ${aliasIndex})`); // Re-inject swap buttons so icon appears immidiately setTimeout(function() { injectSwapNamesButton(wmeSDK); }, 150); } catch (error) { Logger.error('Error swapping venue names:', error); } } function injectSwapNamesButton(wmeSDK) { // Clean up existing observer when selection changes disconnectAliasObserver(); // Only run if a venue is selected const selection = wmeSDK.Editing.getSelection(); if (!selection || selection.objectType !== 'venue' || !selection.ids || selection.ids.length !== 1) { return; } const venueId = selection.ids[0]; const venue = wmeSDK.DataModel.Venues.getById({ venueId }); if (!venue) return; // Setup MutationObserver to watch for changes in aliases list function setupAliasObserver() { const aliasesList = document.querySelector('.aliases-list'); if (!aliasesList) return; try { aliasListObserver = new MutationObserver((mutations) => { let shouldReinject = false; mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'WZ-LIST-ITEM') { shouldReinject = true; break; } } } }); if (shouldReinject) { setTimeout(() => tryInjectSwapButton(), ALIAS_INJECTION_DELAY); } }); aliasListObserver.observe(aliasesList, { childList: true, subtree: true }); } catch (error) { Logger.warn('Error setting up alias observer:', error); } } // Wait for the venue aliases section to exist and inject swap buttons function tryInjectSwapButton() { const $aliasesList = $('.aliases-list'); if ($aliasesList.length === 0) { setTimeout(tryInjectSwapButton, RETRY_INJECTION_DELAY); return; } // Setup observer for this aliases list if not already done if (!aliasListObserver) { setupAliasObserver(); } let foundAliases = false; // Process each alias item and add swap button if needed $aliasesList.find('wz-list-item').each(function (index) { const $aliasItem = $(this); const $actionsContainer = $aliasItem.find('div[slot="actions"].alias-item-actions'); if ($actionsContainer.length === 0) return true; // Continue to next iteration // Remove unwanted "To Name" button from other scripts $actionsContainer.find('div.makePrimary.alias-item-action').filter(function() { return $(this).text().trim() === 'To Name'; }).remove(); // Check if swap button already exists in this specific alias item if ($actionsContainer.find('.swap-names-btn').length > 0) { foundAliases = true; return true; // Continue to next iteration } // Check if venue has both name and aliases before showing button const hasSwappableNames = venue.name && venue.aliases && venue.aliases.length > 0; if (!hasSwappableNames) return true; // Continue to next iteration // Create swap button for this specific alias const buttonHtml = ` <wz-button color="blue" size="sm" class="alias-item-action alias-item-action-swap swap-names-btn" title="Swap primary name with this alias" data-alias-index="${index}"> <i class="w-icon w-icon-arrow-up alias-item-action-icon"></i> </wz-button> `; $actionsContainer.prepend(buttonHtml); foundAliases = true; }); // Fallback method if no aliases found // if (!foundAliases) { // const $nameField = $('input[placeholder*="name" i], input[name*="name" i], .venue-name input, .place-name input'); // if ($nameField.length > 0) { // const $targetContainer = $nameField.closest('.form-group, .field-group, .control-group').first(); // if ($targetContainer.length > 0 && $('.swap-names-btn').length === 0) { // const hasSwappableNames = venue.name && venue.aliases && venue.aliases.length > 0; // if (hasSwappableNames) { // const buttonHtml = ` // <div class='form-group swap-names-container' style='margin: 5px 0; display: inline-block;'> // <wz-button color="blue" size="sm" class="swap-names-btn" title="Swap primary name with first alias" data-alias-index="0"> // <i class="w-icon w-icon-arrow-up"></i> Swap Names // </wz-button> // </div> // `; // $targetContainer.after(buttonHtml); // foundAliases = true; // } // } // } // } // Retry if no aliases found yet if (!foundAliases) { setTimeout(tryInjectSwapButton, RETRY_INJECTION_DELAY); return; } // Attach click handler for all swap buttons $('.swap-names-btn') .off('click.swapnames') .on('click.swapnames', function (e) { e.preventDefault(); e.stopPropagation(); const aliasIndex = parseInt($(this).attr('data-alias-index') || '0', 10); swapPrimaryAndAliasNames(wmeSDK, aliasIndex); }); } // Start the injection process tryInjectSwapButton(); } function injectButtonStation(wmeSDK) { // Only run if a venue is selected const selection = wmeSDK.Editing.getSelection(); if (!selection || selection.objectType !== 'venue' || !selection.ids || selection.ids.length !== 1) return; const venueId = selection.ids[0]; const venue = wmeSDK.DataModel.Venues.getById({ venueId }); const topCountry = wmeSDK.DataModel.Countries.getTopCountry(); const gasStationKey = getGasStationCategoryKey(); const chargingStationKey = getChargingStationCategoryKey(); // Check if venue.categories (array) contains the gas station or charging station key and country is Nepal or Pakistan const isNepal = !!topCountry && (topCountry.name === 'Nepal' || topCountry.code === 'NP'); const isPakistan = !!topCountry && (topCountry.name === 'Pakistan' || topCountry.code === 'PK'); const isGasStation = !!venue && Array.isArray(venue.categories) && venue.categories.includes(gasStationKey); const isChargingStation = !!venue && Array.isArray(venue.categories) && venue.categories.includes(chargingStationKey); // Only show buttons for Nepal gas/charging stations or Pakistan gas stations if (!((isGasStation || isChargingStation) && isNepal) && !(isGasStation && isPakistan)) return; // Show brand buttons for Nepal and Pakistan gas stations, and Nepal charging stations function tryInjectBrandButtons() { const $catControl = $('.categories-control'); if ($catControl.length === 0) { setTimeout(tryInjectBrandButtons, BRAND_BUTTON_RETRY_DELAY); return; } // Prevent duplicate buttons if ($('.gas-station-brand-btn, .charging-station-brand-btn').length > 0) return; // Determine which type of station and get relevant brands let countryBrands = null; let stationTypeName = ''; let buttonClass = ''; let categoryKey = ''; if (isGasStation) { stationTypeName = 'Gas Station'; buttonClass = 'gas-station-brand-btn'; categoryKey = gasStationKey; if (isNepal) { countryBrands = GAS_STATION_BRANDNAME.Nepal.brandnames; } else if (isPakistan) { countryBrands = GAS_STATION_BRANDNAME.Pakistan.brandnames; } } else if (isChargingStation && isNepal) { stationTypeName = 'Charging Station'; buttonClass = 'charging-station-brand-btn'; categoryKey = chargingStationKey; countryBrands = CHARGING_STATION_BRANDNAME.Nepal.brandnames; } if (!countryBrands) return; // Log current brand value for debugging if (isPakistan && isGasStation) { console.log('[Brand Debug] Current venue brand value (Pakistan Gas Station):', venue.brand); } else if (isNepal && isChargingStation) { console.log('[Brand Debug] Current venue brand value (Nepal Charging Station):', venue.brand); } // Build buttons for each brand let buttonsHtml = `<div class='form-group e85 e85-e85-14'><label class='control-label'>Set ${stationTypeName} Brand</label>`; countryBrands.forEach((brandObj) => { buttonsHtml += `<button class='waze-btn waze-btn-small waze-btn-white e85 ${buttonClass}' style='border:2px solid #0078d7;border-radius:4px;margin:2px;' data-primary='${brandObj.primaryName}' data-brand='${ brandObj.brand }' data-website='${brandObj.website || ''}' data-category='${categoryKey}'>${brandObj.primaryName}</button> `; }); buttonsHtml += `</div>`; $catControl.after(buttonsHtml); // Button click handler for both gas station and charging station brands $('.gas-station-brand-btn, .charging-station-brand-btn').on('click', function () { const primaryName = $(this).attr('data-primary'); const brand = $(this).attr('data-brand'); const website = $(this).attr('data-website'); const categoryKey = $(this).attr('data-category'); // Find the selected brand object to get its predefined aliases let selectedBrandObj = null; if (countryBrands) { selectedBrandObj = countryBrands.find(brandObj => brandObj.primaryName === primaryName); } // Read lockRank for the station category from localStorage config let lockRank = null; let config = {}; try { config = JSON.parse(localStorage.getItem('wme-poi-shortcuts-config') || '{}'); } catch (e) { config = {}; } let foundConfig = false; for (let i = 1; i <= 10; i++) { if (config[i] && config[i].category === categoryKey) { lockRank = parseInt(config[i].lock, 10); foundConfig = true; break; } } if (!foundConfig || isNaN(lockRank)) { lockRank = venue.lockRank && !isNaN(venue.lockRank) ? venue.lockRank : 1; } // Build aliases array: start with existing venue aliases, add current name if different, then add brand aliases let aliases = Array.isArray(venue.aliases) ? venue.aliases.slice() : []; // Add current venue name to aliases if it's different from the selected primaryName if (venue.name && venue.name !== primaryName && !aliases.includes(venue.name)) { aliases.push(venue.name); } // Add predefined aliases from the brand data if (selectedBrandObj && Array.isArray(selectedBrandObj.aliases)) { selectedBrandObj.aliases.forEach(alias => { // Only add if it's not empty and not already in the aliases array if (alias && alias.trim() !== '' && !aliases.includes(alias)) { aliases.push(alias); } }); } // Log venue before update const venueBefore = wmeSDK.DataModel.Venues.getById({ venueId }); console.log('[Brand Debug] Venue before update:', venueBefore); console.log('[Brand Debug] Selected brand object:', selectedBrandObj); console.log('[Brand Debug] Final aliases array:', aliases); const updateObj = { venueId: venueId, name: primaryName, aliases: aliases, brand: brand, }; if (website) { updateObj.url = website; } console.log('[Brand Debug] Attempting updateVenue (no lockRank) with:', updateObj); try { wmeSDK.DataModel.Venues.updateVenue(updateObj); console.log('[Brand Debug] updateVenue (no lockRank) called successfully.'); // Log venue after update setTimeout(() => { const venueAfter = wmeSDK.DataModel.Venues.getById({ venueId }); console.log('[Brand Debug] Venue after update:', venueAfter); }, 500); // Now update lockRank in a separate call if (lockRank !== undefined && lockRank !== null) { setTimeout(() => { try { wmeSDK.DataModel.Venues.updateVenue({ venueId: venueId, lockRank: lockRank }); console.log('[Brand Debug] lockRank updated successfully:', lockRank); } catch (err2) { console.warn('[Brand Debug] lockRank update failed:', err2); } }, 300); } } catch (err) { console.warn('[Brand Debug] Update failed:', err); } }); } tryInjectBrandButtons(); } async function registerSidebarScriptTab(wmeSDK) { // Register a script tab in the Scripts sidebar try { const { tabLabel, tabPane } = await wmeSDK.Sidebar.registerScriptTab(); // Add label/icon to the tab tabLabel.innerHTML = '<span style="display:flex;align-items:center;"><span style="font-size:16px;margin-right:4px;">⭐</span>POI Shortcuts</span>'; // Use buildAllItemOptions to show all 10 dropdowns with script info header tabPane.innerHTML = ` <div id='wme-poi-shortcuts-content'> <div style="padding: 8px 16px; background: #f5f5f5; border-bottom: 1px solid #ddd; margin-bottom: 10px;"> <div style="font-weight: bold; font-size: 14px; color: #333;">${scriptName}</div> <div style="font-size: 12px; color: #666;">${scriptVersion}</div> </div> ${buildGLEControls()} ${buildAllItemOptions()} </div>`; // Add event listeners for GLE controls setTimeout(() => { const cbEnableGLE = document.getElementById('_cbEnableGLE'); if (cbEnableGLE) { // Restore checkbox state from localStorage cbEnableGLE.checked = !!gleEnabled; cbEnableGLE.addEventListener('change', function () { // Save state to localStorage localStorage.setItem('wme-poi-shortcuts-gle-enabled', JSON.stringify(this.checked)); if (this.checked) { // Enable GLE functionality if (GLE && typeof GLE.enable === 'function') { GLE.enable(); } } else { // Disable GLE functionality completely if (GLE && typeof GLE.disable === 'function') { GLE.disable(); } // Force map refresh to remove lingering highlights setTimeout(() => { if (typeof W !== 'undefined' && W.map && W.map.getOLMap()) { const olMap = W.map.getOLMap(); if (olMap && typeof olMap.redraw === 'function') { olMap.redraw(); } } }, 100); } // Update GLE enabled state if (GLE) { GLE.enabled = this.checked; } }); } }, 0); } catch (e) { console.error('Failed to register POI Shortcuts script tab:', e); } } function scriptupdatemonitor() { if (WazeWrap?.Ready) { bootstrap({ scriptUpdateMonitor: { downloadUrl } }); WazeWrap.Interface.ShowScriptUpdate(scriptName, scriptVersion, updateMessage, downloadUrl, forumURL); } else { setTimeout(scriptupdatemonitor, SCRIPT_UPDATE_MONITOR_DELAY); } } // Start the "scriptupdatemonitor" scriptupdatemonitor(); console.log(`${scriptName} initialized.`); /******************************************Changelogs*********************************************************** 2025.08.19.04 - Enhanced "Convert OTHER to Residential" with professional code refactoring and improved reliability. - Added comprehensive JSDoc documentation and modular function structure for better maintainability. - Improved error handling, user feedback, and multiple fallback strategies for button clicking. - Now fully automates the conversion process including clicking the "Convert to residential" button. - Compatible with new WME v2.309 house number types for both Residential Point Places (RPP) and venues. 2025.08.19.03 - Added new shortcut "Convert OTHER to Residential" that prepares OTHER type venues for residential conversion. - When conditions are met (venue is OTHER type, name matches pattern like building numbers with Cyrillic characters, no existing house numbers), the function copies the primary name to house numbers. User then manually clicks WME's "Convert to residential" button. - Simplified functionality to let WME handle the actual conversion process (RPP, etc.). 2025.08.18.01 - Fix for multiple name swapping buttons displayed when used with WME PIE. 2025.08.17.02 - Fix for swap button not appearing on first venue selection after page refresh. - Added initial venue selection check after WME ready event. 2025.08.17.01 - Re-inject swap buttons so icon appears immediately. 2025.08.16.03 - Fix for bug where POI points were not being selected correctly after creation. 2025.08.16.02 - Fix for bug where alt names were not adding correctly for gas stations and charging stations button clicked. - Charging stations button for Nepal has been added. 2025.08.16.01 - Fix for bug where gas station failed to save when gas station button pressed. 2025.08.15.03 - Added automatic hazard layer group and individual layer enabling for hazard shortcuts. - Added support for Sharp Curves. - Added support for Complex Junctions. - Added support for Multiple Lanes Merge. 2025.08.11.04 - Added support for updating Pakistan Petroleum brands using buttons. - Minor bug fixes. 2025.08.11.03 - Added support for updating Pakistan Petroleum brands using buttons. - Added button colours 2025.08.10.15 - Enhanced swap names functionality with arrow-up buttons for all aliases - Improved button visibility with white icons and proper positioning before delete buttons - Added support for swapping primary name with any specific alias (not just first one) 2025.08.10.14 - Added swap names functionality between primary and alias names using WME SDK 2025.08.10.011 - Legacy shortcuts key support ******************************************************************************************************************/ })();