您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Various UI changes to make editing faster and easier.
当前为
// ==UserScript== // @name WME POI Shortcuts // @namespace https://gf.qytechs.cn/users/45389 // @version 2025.08.16.01 // @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 = ` Fix for bug where gas station failed to save when gas station button pressed`; 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) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_toll_booth', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Toll Booth</b>`, false, false, 2000); $("wz-icon[name='toll-booth']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_level-crossing', title: 'Add Level Crossing', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_railroad_crossing', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Level Crossing</b>`, false, false, 2000); $("wz-icon[name='railway-crossing']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_school-zone', title: 'Create School Zone', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_school_zone', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>School Zone</b>`, false, false, 2000); $("wz-icon[name='school-zone']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_sharp-curves', title: 'Create Sharp Curves', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_dangerous_curve', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Sharp Curves</b>`, false, false, 2000); $("wz-icon[name='sharp-curve-ahead']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_complex-junctions', title: 'Create Complex Junctions', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_dangerous_intersection', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Complex Junctions</b>`, false, false, 2000); $("wz-icon[name='dangerous-intersection']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, { handler: 'WME-POI-Shortcuts_multiple-lanes-merging', title: 'Create Multiple Lanes Merging', func: function (ev) { ensureHazardLayersEnabled('layer-switcher-item_permanent_hazard_dangerous_merge', () => { WazeWrap.Alerts.info('POI Shortcut', `POI Type: <b>Multiple Lanes Merging</b>`, false, false, 2000); $("wz-icon[name='merge-ahead']").parent().trigger('click'); }); }, key: -1, // No default key, user can set custom arg: {}, }, ]; // 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; } // Show WazeWrap alert with POI info before drawing const poiName = $(`#poiItem${slotNumber} option:selected`).text(); const lockLevel = !isNaN(lock) ? parseInt(lock, 10) + 1 : 1; const areaType = geomType === 'point' ? 'Point' : 'Area'; WazeWrap.Alerts.info('POI Shortcut', `Selected POI Name: <b>${poiName}</b><br>Lock Level: <b>${lockLevel}</b><br>Type: <b>${areaType}</b>`, false, false, 2500); // Geometry: area = drawPolygon, point = drawPoint let drawPromise = geomType === 'point' ? wmeSDK.Map.drawPoint() : wmeSDK.Map.drawPolygon(); drawPromise .then((geometry) => { let newVenue = wmeSDK.DataModel.Venues.addVenue({ category: cat, geometry: geometry, }); 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((err) => { if (err && err.name === 'InvalidStateError') { console.log('POI drawing was cancelled by the user.'); } else { console.error('Error during POI drawing:', err); } }); } catch (error) { console.error(`Error creating POI from shortcut ${slotNumber}:`, error); } } // Helper function to ensure hazard layer group and specific hazard layer are enabled function ensureHazardLayersEnabled(hazardLayerId, callback) { try { // Wait a bit to ensure the layer UI is ready setTimeout(() => { // First, ensure the permanent hazards group is enabled const hazardGroupToggle = document.getElementById('layer-switcher-group_permanent_hazards'); if (hazardGroupToggle) { // For wz-toggle-switch: checked="" means enabled, checked="false" means disabled const checkedAttr = hazardGroupToggle.getAttribute('checked'); const isGroupEnabled = checkedAttr === '' || checkedAttr === 'true'; if (!isGroupEnabled) { hazardGroupToggle.click(); // Wait for the group to be enabled before enabling individual layers setTimeout(() => { enableSpecificHazardLayer(hazardLayerId, callback); }, 400); return; } } else { console.warn('Hazard group toggle not found'); } // If group is already enabled, directly enable the specific layer enableSpecificHazardLayer(hazardLayerId, callback); }, 50); } catch (error) { console.error('Error enabling hazard layers:', error); // Execute callback even if there's an error to prevent hanging if (callback && typeof callback === 'function') { setTimeout(callback, 100); } } } // Helper function to enable a specific hazard layer function enableSpecificHazardLayer(hazardLayerId, callback) { try { if (hazardLayerId) { const hazardLayerCheckbox = document.getElementById(hazardLayerId); if (hazardLayerCheckbox) { // For wz-checkbox: checked="" means enabled, checked="false" means disabled const checkedAttr = hazardLayerCheckbox.getAttribute('checked'); const isLayerEnabled = checkedAttr === '' || checkedAttr === 'true'; if (!isLayerEnabled) { hazardLayerCheckbox.click(); // Wait for layer to be enabled before executing callback setTimeout(() => { if (callback && typeof callback === 'function') { callback(); } }, 300); } else { // Layer is already enabled, execute callback immediately if (callback && typeof callback === 'function') { callback(); } } } else { console.warn(`Hazard layer element not found: ${hazardLayerId}`); // Execute callback even if element not found to prevent hanging if (callback && typeof callback === 'function') { setTimeout(callback, 100); } } } else { // No specific layer ID provided, execute callback if (callback && typeof callback === 'function') { callback(); } } } catch (error) { console.error('Error enabling specific hazard layer:', error); // Execute callback even if there's an error to prevent hanging if (callback && typeof callback === 'function') { setTimeout(callback, 100); } } } // --- 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); const updateObj = { venueId: venueId, name: primaryName, aliases: aliases, brand: brand, }; if (website) { updateObj.url = website; } console.log('[Brand Debug] Attempting updateVenue (no lockRank) with:', updateObj); try { wmeSDK.DataModel.Venues.updateVenue(updateObj); console.log('[Brand Debug] updateVenue (no lockRank) called successfully.'); // Log venue after update setTimeout(() => { const venueAfter = wmeSDK.DataModel.Venues.getById({ venueId }); console.log('[Brand Debug] Venue after update:', venueAfter); }, 500); // Now update lockRank in a separate call if (lockRank !== undefined && lockRank !== null) { setTimeout(() => { try { wmeSDK.DataModel.Venues.updateVenue({ venueId: venueId, lockRank: lockRank }); console.log('[Brand Debug] lockRank updated successfully:', lockRank); } catch (err2) { console.warn('[Brand Debug] lockRank update failed:', err2); } }, 300); } } catch (err) { console.warn('[Brand Debug] Update failed:', err); } }); } tryInjectBrandButtons(); } async function registerSidebarScriptTab(wmeSDK) { // Register a script tab in the Scripts sidebar try { const { tabLabel, tabPane } = await wmeSDK.Sidebar.registerScriptTab(); // Add label/icon to the tab tabLabel.innerHTML = '<span style="display:flex;align-items:center;"><span style="font-size:16px;margin-right:4px;">⭐</span>POI Shortcuts</span>'; // Use buildAllItemOptions to show all 10 dropdowns with script info header tabPane.innerHTML = ` <div id='wme-poi-shortcuts-content'> <div style="padding: 8px 16px; background: #f5f5f5; border-bottom: 1px solid #ddd; margin-bottom: 10px;"> <div style="font-weight: bold; font-size: 14px; color: #333;">${scriptName}</div> <div style="font-size: 12px; color: #666;">${scriptVersion}</div> </div> ${buildGLEControls()} ${buildAllItemOptions()} </div>`; // Add event listeners for GLE controls setTimeout(() => { const cbEnableGLE = document.getElementById('_cbEnableGLE'); if (cbEnableGLE) { // Restore checkbox state from localStorage cbEnableGLE.checked = !!gleEnabled; cbEnableGLE.addEventListener('change', function () { // Save state to localStorage localStorage.setItem('wme-poi-shortcuts-gle-enabled', JSON.stringify(this.checked)); if (this.checked) { // Enable GLE functionality if (GLE && typeof GLE.enable === 'function') { GLE.enable(); } } else { // Disable GLE functionality completely if (GLE && typeof GLE.disable === 'function') { GLE.disable(); } // Force map refresh to remove lingering highlights setTimeout(() => { if (typeof W !== 'undefined' && W.map && W.map.getOLMap()) { const olMap = W.map.getOLMap(); if (olMap && typeof olMap.redraw === 'function') { olMap.redraw(); } } }, 100); } // Update GLE enabled state if (GLE) { GLE.enabled = this.checked; } }); } }, 0); } catch (e) { console.error('Failed to register POI Shortcuts script tab:', e); } } function scriptupdatemonitor() { if (WazeWrap?.Ready) { bootstrap({ scriptUpdateMonitor: { downloadUrl } }); WazeWrap.Interface.ShowScriptUpdate(scriptName, scriptVersion, updateMessage, downloadUrl, forumURL); } else { setTimeout(scriptupdatemonitor, 250); } } // Start the "scriptupdatemonitor" scriptupdatemonitor(); console.log(`${scriptName} initialized.`); /******************************************Changelogs*********************************************************** 2025.08.16.01 - Fix for bug where gas station failed to save when gas station button pressed. 2025.08.15.03 - Added automatic hazard layer group and individual layer enabling for hazard shortcuts. - Added support for Sharp Curves. - Added support for Complex Junctions. - Added support for Multiple Lanes Merge. 2025.08.11.04 - Added support for updating Pakistan Petroleum brands using buttons. - Minor bug fixes. 2025.08.11.03 - Added support for updating Pakistan Petroleum brands using buttons. - Added button colours 2025.08.10.15 - Enhanced swap names functionality with arrow-up buttons for all aliases - Improved button visibility with white icons and proper positioning before delete buttons - Added support for swapping primary name with any specific alias (not just first one) 2025.08.10.14 - Added swap names functionality between primary and alias names using WME SDK 2025.08.10.011 - Legacy shortcuts key support ******************************************************************************************************************/ })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址