// ==UserScript==
// @name WME POI Shortcuts
// @namespace https://gf.qytechs.cn/users/45389
// @version 2025.08.11.04
// @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 gf.qytechs.cn
// @contributionURL https://github.com/WazeDev/Thank-The-Authors
// @grant GM_xmlhttpRequest
// @grant GM_addElement
// @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
// @require https://update.gf.qytechs.cn/scripts/509664/WME%20Utils%20-%20Bootstrap.js
// @require https://gf.qytechs.cn/scripts/523706-google-link-enhancer/code/Link%20Enhancer.js
// ==/UserScript==
/* global WazeWrap */
/* global bootstrap */
https: (function () {
('use strict');
const updateMessage = `
Added support for updating Pakistan Petroleum brands using buttons.\n Minor bug fixes`;
const scriptName = GM_info.script.name;
const scriptVersion = GM_info.script.version;
const downloadUrl = 'https://gf.qytechs.cn/scripts/545278-wme-poi-shortcuts/code/wme-poi-shortcuts.user.js';
const forumURL = 'https://gf.qytechs.cn/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',
},
],
},
};
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 `
<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);
});
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 = `<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 })) {
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 = `
<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);
}
}
});
}
// 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;
}
}
}
}
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 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);
if (!(isGasStation && (isNepal || isPakistan))) return;
// Show brand buttons for Nepal and Pakistan gas stations
function tryInjectBrandButtons() {
const $catControl = $('.categories-control');
if ($catControl.length === 0) {
setTimeout(tryInjectBrandButtons, 150);
return;
}
// Prevent duplicate buttons
if ($('.gas-station-brand-btn').length > 0) return;
// Determine country and get only relevant brands
let countryBrands = null;
if (isNepal) {
countryBrands = GAS_STATION_BRANDNAME.Nepal.brandnames;
} else if (isPakistan) {
countryBrands = GAS_STATION_BRANDNAME.Pakistan.brandnames;
}
if (!countryBrands) return;
// Log current brand value for Pakistan gas stations
if (isPakistan) {
console.log('[Brand Debug] Current venue brand value (Pakistan):', venue.brand);
}
// Build buttons for each brand
let buttonsHtml = `<div class='form-group e85 e85-e85-14'><label class='control-label'>Set Station Brand</label>`;
countryBrands.forEach((brandObj) => {
buttonsHtml += `<button class='waze-btn waze-btn-small waze-btn-white e85 gas-station-brand-btn' style='border:2px solid #0078d7;border-radius:4px;margin:2px;' data-primary='${brandObj.primaryName}' data-brand='${
brandObj.brand
}' data-website='${brandObj.website || ''}'>${brandObj.primaryName}</button> `;
});
buttonsHtml += `</div>`;
$catControl.after(buttonsHtml);
// Button click handler
$('.gas-station-brand-btn').on('click', function () {
const primaryName = $(this).attr('data-primary');
const brand = $(this).attr('data-brand');
const website = $(this).attr('data-website');
// 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);
foundConfig = true;
break;
}
}
if (!foundConfig || isNaN(lockRank)) {
lockRank = venue.lockRank && !isNaN(venue.lockRank) ? venue.lockRank : 1;
}
// Move current name to aliases if not the selected primaryName
let aliases = Array.isArray(venue.aliases) ? venue.aliases.slice() : [];
if (venue.name && venue.name !== primaryName && !aliases.includes(venue.name)) {
aliases.push(venue.name);
}
// Log venue before update
const venueBefore = wmeSDK.DataModel.Venues.getById({ venueId });
console.log('[Brand Debug] Venue before update:', venueBefore);
// Try both 'brand' and 'brandName' fields
const updateObj = {
venueId: venueId,
name: primaryName,
aliases: aliases,
brand: brand,
brandName: 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, 250);
}
}
// Start the "scriptupdatemonitor"
scriptupdatemonitor();
console.log(`${scriptName} initialized.`);
/*Changelogs
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
*/
})();