// ==UserScript== // @name WME POI Shortcuts // @namespace https://greasyfork.org/users/45389 // @version 2025.08.10.15 // @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.icu/scripts/509664/WME%20Utils%20-%20Bootstrap.js // @require https://greasyfork.org/scripts/523706-google-link-enhancer/code/Link%20Enhancer.js // @downloadURL none // ==/UserScript== /* global WazeWrap */ /* global bootstrap */ https: (function () { ('use strict'); const updateMessage = ` Added swap names functionality - promotes alias to primary name with arrow-up buttons next to each alias`; 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'; 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 { console.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 `
`; } 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); }); 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: () => { injectNOCButtonIfNepalGasStation(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 = `'; return html; } function buildLockLevelDropdown(itemNumber) { // Show lock dropdown for all 10 items let html = `'; return html; } function buildGeometryTypeDropdown(itemNumber) { // Dropdown for geometry type: Point or Area return ``; } function buildItemOption(itemNumber) { var $section = $('
', { style: 'padding:4px 8px;font-size:10px;', id: 'poiPlaceCat' + itemNumber }); $section.html( [ `Item ${itemNumber}`, buildItemList(itemNumber), `
${buildLockLevelDropdown(itemNumber)} ${buildGeometryTypeDropdown(itemNumber)}
`, ].join(' ') ); return $section.html(); } function buildAllItemOptions() { let html = ''; for (let i = 1; i <= 10; i++) { html += buildItemOption(i); } 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 })) { console.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) { $("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) { $("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) { $("wz-icon[name='school-zone']").parent().trigger('click'); }, key: -1, // No default key, user can set custom 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 ); } // 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 === '') { console.warn(`POI Shortcut ${slotNumber}: No category selected`); return; } // 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', }); } }); } catch (error) { console.error(`Error creating POI from shortcut ${slotNumber}:`, error); } } // --- 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) { console.log('WMEKSLoadKeyboardShortcuts(' + scriptName + ')'); if (localStorage[scriptName + 'KBS']) for (var shortcuts = JSON.parse(localStorage[scriptName + 'KBS']), i = 0; i < shortcuts.length; i++) try { W.accelerators._registerShortcuts(shortcuts[i]); } catch (error) { console.log(error); } } function WMEKSSaveKeyboardShortcuts(scriptName) { console.log('WMEKSSaveKeyboardShortcuts(' + scriptName + ')'); var 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 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) { console.warn('No venue selected for name swapping'); return; } const venueId = selection.ids[0]; const venue = wmeSDK.DataModel.Venues.getById({ venueId }); if (!venue) { console.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) { console.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) { console.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, }); console.log(`Swapped names: "${currentPrimaryName}" ↔ "${targetAlias}" (alias index: ${aliasIndex})`); } catch (error) { console.error('Error swapping venue names:', error); } } function injectSwapNamesButton(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 }); if (!venue) return; // Wait for the venue aliases section to exist function tryInjectSwapButton() { // Look for the aliases list and inject button into ALL alias items' actions containers const $aliasesList = $('.aliases-list'); let foundAliases = false; if ($aliasesList.length > 0) { // Find ALL alias items and add swap button to each $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) { // Check if swap button already exists in this specific alias item if ($actionsContainer.find('.swap-names-btn').length === 0) { foundAliases = true; // 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 (swap with the alias at this index) const buttonHtml = ` `; $actionsContainer.prepend(buttonHtml); } } }); } // 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 = `
Swap Names
`; $targetContainer.after(buttonHtml); foundAliases = true; } } } } if (!foundAliases) { setTimeout(tryInjectSwapButton, 100); return; } // Button click handler for all swap buttons $('.swap-names-btn').off('click.swapnames').on('click.swapnames', function (e) { e.preventDefault(); const aliasIndex = parseInt($(this).attr('data-alias-index') || '0', 10); swapPrimaryAndAliasNames(wmeSDK, aliasIndex); }); } tryInjectSwapButton(); } function injectNOCButtonIfNepalGasStation(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(); // Check if venue.categories (array) contains the gas station key const isNepalGasStation = !!venue && !!topCountry && (topCountry.name === 'Nepal' || topCountry.code === 'NP') && Array.isArray(venue.categories) && venue.categories.includes(gasStationKey); if (!isNepalGasStation) return; // Wait for the categories-control element to exist function tryInject() { const $catControl = $('.categories-control'); if ($catControl.length === 0) { setTimeout(tryInject, 150); // Retry after 150ms return; } // Prevent duplicate button if ($('.noc-gas-station-btn').length > 0) return; // Inject button after categories-control const buttonHtml = `
`; $catControl.after(buttonHtml); // Button click handler $('.noc-gas-station-btn').on('click', function () { // Read lockRank for GAS_STATION 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 === gasStationKey) { lockRank = parseInt(config[i].lock, 10); console.log(`[NOC Debug] Found gas station shortcut config: slot=${i}, lockRank=${lockRank}`); foundConfig = true; break; } } if (!foundConfig || isNaN(lockRank)) { console.log(`[NOC Debug] Using fallback lockRank. venue.lockRank=${venue.lockRank}`); lockRank = venue.lockRank && !isNaN(venue.lockRank) ? venue.lockRank : 1; } console.log(`[NOC Debug] Final lockRank to be used: ${lockRank}`); // Move current name to aliases if not 'NOC' if (venue.name !== 'NOC') { let aliases = Array.isArray(venue.aliases) ? venue.aliases.slice() : []; if (venue.name && !aliases.includes(venue.name)) { aliases.push(venue.name); } const updateObj = { venueId: venueId, name: 'NOC', aliases: aliases, }; if (venue.brand !== 'Nepal Oil Corporation') { updateObj.brand = 'Nepal Oil Corporation'; console.log('[NOC Debug] Brand updated to Nepal Oil Corporation'); } else { console.log('[NOC Debug] Brand already Nepal Oil Corporation, skipping brand update'); } if (venue.lockRank !== lockRank && (!venue.isLocked || venue.isLocked === false)) { updateObj.lockRank = lockRank; console.log(`[NOC Debug] lockRank updated to ${lockRank}`); } else { console.log(`[NOC Debug] lockRank already ${venue.lockRank}, skipping lockRank update`); } try { wmeSDK.DataModel.Venues.updateVenue(updateObj); } catch (err) { console.warn('[NOC Debug] Update failed:', err); } } else { const updateObj = { venueId: venueId, }; if (venue.brand !== 'Nepal Oil Corporation') { updateObj.brand = 'Nepal Oil Corporation'; console.log('[NOC Debug] Brand updated to Nepal Oil Corporation'); } else { console.log('[NOC Debug] Brand already Nepal Oil Corporation, skipping brand update'); } if (venue.lockRank !== lockRank && (!venue.isLocked || venue.isLocked === false)) { updateObj.lockRank = lockRank; console.log(`[NOC Debug] lockRank updated to ${lockRank}`); } else { console.log(`[NOC Debug] lockRank already ${venue.lockRank}, skipping lockRank update`); } try { wmeSDK.DataModel.Venues.updateVenue(updateObj); } catch (err) { console.warn('[NOC Debug] Update failed:', err); } } }); } tryInject(); } 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 = 'POI Shortcuts'; // Use buildAllItemOptions to show all 10 dropdowns with script info header tabPane.innerHTML = `
${scriptName}
${scriptVersion}
${buildGLEControls()} ${buildAllItemOptions()}
`; // 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, 250); } } // Start the "scriptupdatemonitor" scriptupdatemonitor(); console.log(`${scriptName} initialized.`); /*Changelogs 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 */ })();