Greasy Fork

Greasy Fork is available in English.

WME POI Shortcuts

Various UI changes to make editing faster and easier.

当前为 2025-08-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            WME POI Shortcuts
// @namespace       http://greasyfork.icu/users/45389
// @version         2025.08.11.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         http://greasyfork.icu/scripts/24851-wazewrap/code/WazeWrap.js
// @require         https://update.greasyfork.icu/scripts/509664/WME%20Utils%20-%20Bootstrap.js
// @require         http://greasyfork.icu/scripts/523706-google-link-enhancer/code/Link%20Enhancer.js
// ==/UserScript==

/* global WazeWrap */
/* global bootstrap */

https: (function () {
  ('use strict');

  // 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 updateMessage = `
Added support for updating Pakistan Petroleum brands using buttons.`;
  const scriptName = GM_info.script.name;
  const scriptVersion = GM_info.script.version;
  const downloadUrl = 'http://greasyfork.icu/scripts/545278-wme-poi-shortcuts/code/wme-poi-shortcuts.user.js';
  const forumURL = 'http://greasyfork.icu/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 `
    <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.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
  */
})();