您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Navigate through map history using Alt + arrow keys, mouse control, clickable history sidebar, and location highlighting
// ==UserScript== // @name WME Map Nav History // @name:de WME Karten-Navigations-Historie // @name:es WME Historial de Navegación del Mapa // @name:fr WME Historique de Navigation de Carte // @name:it WME Cronologia di Navigazione Mappa // @description Navigate through map history using Alt + arrow keys, mouse control, clickable history sidebar, and location highlighting // @description:de Ermöglicht die Navigation durch die Kartenhistorie mit Alt + Pfeiltasten, Maussteuerung, klickbarer Historie-Sidebar und Ortsmarkierungen // @description:es Permite navegar por el historial del mapa usando Alt + teclas de flecha, control del ratón, barra lateral de historial clicable y resaltado de ubicaciones // @description:fr Permet de naviguer dans l'historique de la carte en utilisant Alt + touches fléchées, contrôle de la souris, barre latérale d'historique cliquable et mise en évidence des emplacements // @description:it Consente di navigare nella cronologia della mappa utilizzando Alt + tasti freccia, controllo del mouse, barra laterale della cronologia cliccabile e evidenziazione delle posizioni // @namespace https://greasyfork.org/de/users/863740-horst-wittlich // @version 2025.06.04 // @author hiwi234 // @include https://www.waze.com/editor* // @include https://www.waze.com/*/editor* // @include https://beta.waze.com/* // @exclude https://www.waze.com/user/*editor/* // @exclude https://www.waze.com/*/user/*editor/* // @grant none // @license MIT // ==/UserScript== /* global W, OpenLayers */ (function() { 'use strict'; let navigationHistory = []; let currentIndex = -1; let isInitialized = false; let lastSaveTime = 0; let currentLanguage = (navigator.language || navigator.userLanguage).substring(0, 2); let highlightedPositions = {}; const translations = { en: { scriptActive: "Script active", back: "Back", forward: "Forward", controls: "Controls", previousPosition: "Previous position", nextPosition: "Next position", backButton: "Back button", forwardButton: "Forward button", hints: "Hints", historyLimit: "History stores up to 100 positions", autoSave: "New positions are automatically saved when moving the map", history: "History", clearHistory: "Clear History", noHistory: "No history available", currentPosition: "Current Position", markPosition: "Mark Position", unmarkPosition: "Unmark Position", markedPositions: "Marked Positions", clickToNavigate: "Click on history entry for direct jump", autoSaveDescription: "Automatic save and loading on next start", confirmClearHistory: "Are you sure you want to delete the entire history?", locationName: "Location", zoomLevel: "Zoom Level" }, de: { scriptActive: "Script aktiv", back: "Zurück", forward: "Vorwärts", controls: "Steuerung", previousPosition: "Vorherige Position", nextPosition: "Nächste Position", backButton: "Zurück-Button", forwardButton: "Vorwärts-Button", hints: "Hinweise", historyLimit: "Die Historie speichert bis zu 100 Positionen", autoSave: "Neue Positionen werden beim Verschieben der Karte automatisch gespeichert", history: "Verlauf", clearHistory: "Verlauf löschen", noHistory: "Kein Verlauf verfügbar", currentPosition: "Aktuelle Position", markPosition: "Position markieren", unmarkPosition: "Position entfernen", markedPositions: "Markierte Positionen", clickToNavigate: "Klick auf Historie-Eintrag für direkten Sprung", autoSaveDescription: "Verlauf wird automatisch gespeichert und beim nächsten Start geladen", confirmClearHistory: "Sind Sie sicher, dass Sie den gesamten Verlauf löschen möchten?", locationName: "Ort", zoomLevel: "Zoom-Stufe" }, es: { scriptActive: "Script activo", back: "Atrás", forward: "Adelante", controls: "Controles", previousPosition: "Posición anterior", nextPosition: "Siguiente posición", backButton: "Botón atrás", forwardButton: "Botón adelante", hints: "Consejos", historyLimit: "El historial almacena hasta 100 posiciones", autoSave: "Las nuevas posiciones se guardan automáticamente al mover el mapa", history: "Historial", clearHistory: "Limpiar Historial", noHistory: "Sin historial disponible", currentPosition: "Posición Actual", markPosition: "Marcar Posición", unmarkPosition: "Desmarcar Posición", markedPositions: "Posiciones Marcadas", clickToNavigate: "Haz clic en la entrada del historial para saltar directamente", autoSaveDescription: "Guardado automático y carga en el próximo inicio", confirmClearHistory: "¿Estás seguro de que quieres eliminar todo el historial?", locationName: "Ubicación", zoomLevel: "Nivel de Zoom" }, fr: { scriptActive: "Script actif", back: "Retour", forward: "Avancer", controls: "Contrôles", previousPosition: "Position précédente", nextPosition: "Position suivante", backButton: "Bouton retour", forwardButton: "Bouton avancer", hints: "Conseils", historyLimit: "L'historique stocke jusqu'à 100 positions", autoSave: "Les nouvelles positions sont automatiquement sauvegardées lors du déplacement de la carte", history: "Historique", clearHistory: "Effacer l'Historique", noHistory: "Aucun historique disponible", currentPosition: "Position Actuelle", markPosition: "Marquer la Position", unmarkPosition: "Démarquer la Position", markedPositions: "Positions Marquées", clickToNavigate: "Cliquez sur l'entrée d'historique pour un saut direct", autoSaveDescription: "Sauvegarde automatique et chargement au prochain démarrage", confirmClearHistory: "Êtes-vous sûr de vouloir supprimer tout l'historique?", locationName: "Emplacement", zoomLevel: "Niveau de Zoom" }, it: { scriptActive: "Script attivo", back: "Indietro", forward: "Avanti", controls: "Controlli", previousPosition: "Posizione precedente", nextPosition: "Posizione successiva", backButton: "Pulsante indietro", forwardButton: "Pulsante avanti", hints: "Suggerimenti", historyLimit: "La cronologia memorizza fino a 100 posizioni", autoSave: "Le nuove posizioni vengono salvate automaticamente quando si sposta la mappa", history: "Cronologia", clearHistory: "Cancella Cronologia", noHistory: "Nessuna cronologia disponibile", currentPosition: "Posizione Attuale", markPosition: "Marca Posizione", unmarkPosition: "Rimuovi Marcatura", markedPositions: "Posizioni Marcate", clickToNavigate: "Clicca sulla voce della cronologia per il salto diretto", autoSaveDescription: "Salvataggio automatico e caricamento al prossimo avvio", confirmClearHistory: "Sei sicuro di voler eliminare l'intera cronologia?", locationName: "Posizione", zoomLevel: "Livello di Zoom" } }; const t = (key) => { return (translations[currentLanguage] && translations[currentLanguage][key]) || translations.en[key]; }; // Hilfsfunktion um Koordinaten zu formatieren function formatCoords(lat, lon) { return `${lat.toFixed(5)}, ${lon.toFixed(5)}`; } // Hilfsfunktion um Zeitstempel zu formatieren function formatTime(timestamp) { const date = new Date(timestamp); return date.toLocaleTimeString(); } // Hilfsfunktion um Ortsname zu generieren (vereinfacht) function generateLocationName(lat, lon, zoom) { return `${t('zoomLevel')} ${zoom} - ${formatCoords(lat, lon)}`; } // Markierte Position hinzufügen/entfernen function togglePositionHighlight(index) { const position = navigationHistory[index]; if (!position) return; if (highlightedPositions[index]) { // Hervorhebung entfernen delete highlightedPositions[index]; position.marked = false; } else { // Hervorhebung hinzufügen highlightedPositions[index] = true; position.marked = true; } updateHistoryList(); saveToLocalStorage(); } function saveCurrentPosition() { if (!isInitialized || !W.map) return; const now = Date.now(); // Mindestens 2 Sekunden zwischen den Speicherungen if (now - lastSaveTime < 2000) return; const center = W.map.getCenter(); const zoom = W.map.getZoom(); const lastEntry = navigationHistory[currentIndex]; if (lastEntry && Math.abs(lastEntry.lat - center.lat) < 0.00001 && Math.abs(lastEntry.lon - center.lon) < 0.00001 && lastEntry.zoom === zoom) { return; } // Entferne alle Einträge nach dem aktuellen Index (für Verzweigungen) navigationHistory = navigationHistory.slice(0, currentIndex + 1); navigationHistory.push({ lat: center.lat, lon: center.lon, zoom: zoom, timestamp: now, name: generateLocationName(center.lat, center.lon, zoom), marked: false }); currentIndex++; lastSaveTime = now; // Begrenze auf 100 Einträge if (navigationHistory.length > 100) { const removedEntry = navigationHistory.shift(); currentIndex--; // Entferne Hervorhebung des gelöschten Eintrags if (removedEntry.marked && highlightedPositions[0]) { delete highlightedPositions[0]; } // Aktualisiere Hervorhebungs-Indizes const newHighlightedPositions = {}; Object.keys(highlightedPositions).forEach(oldIndex => { const newIndex = parseInt(oldIndex) - 1; if (newIndex >= 0) { newHighlightedPositions[newIndex] = highlightedPositions[oldIndex]; } }); highlightedPositions = newHighlightedPositions; } updateNavigationButtons(); updateHistoryList(); saveToLocalStorage(); } function navigateToPosition(position, index) { if (!position || !isInitialized || !W.map) return; try { const lonlat = new OpenLayers.LonLat(position.lon, position.lat); W.map.setCenter(lonlat, position.zoom); if (typeof index !== 'undefined') { currentIndex = index; updateNavigationButtons(); updateHistoryList(); } } catch (error) { console.error('WME Map Nav History: Navigation error:', error); } } function handleKeyDown(e) { if (!isInitialized) return; if (e.altKey && e.keyCode === 37) { // Alt + Left navigateBack(); e.preventDefault(); } else if (e.altKey && e.keyCode === 39) { // Alt + Right navigateForward(); e.preventDefault(); } } function navigateBack() { if (currentIndex > 0) { currentIndex--; navigateToPosition(navigationHistory[currentIndex]); updateNavigationButtons(); updateHistoryList(); } } function navigateForward() { if (currentIndex < navigationHistory.length - 1) { currentIndex++; navigateToPosition(navigationHistory[currentIndex]); updateNavigationButtons(); updateHistoryList(); } } function updateNavigationButtons() { const backBtn = document.getElementById('nav-history-back'); const forwardBtn = document.getElementById('nav-history-forward'); if (backBtn) { backBtn.disabled = currentIndex <= 0; backBtn.style.opacity = currentIndex <= 0 ? '0.5' : '1'; } if (forwardBtn) { forwardBtn.disabled = currentIndex >= navigationHistory.length - 1; forwardBtn.style.opacity = currentIndex >= navigationHistory.length - 1 ? '0.5' : '1'; } } function updateHistoryList() { const historyContainer = document.getElementById('nav-history-list'); if (!historyContainer) return; if (navigationHistory.length === 0) { historyContainer.innerHTML = `<div class="no-history">${t('noHistory')}</div>`; return; } let html = ''; navigationHistory.forEach((entry, index) => { const isCurrent = index === currentIndex; const isMarked = entry.marked || false; const time = formatTime(entry.timestamp); html += ` <div class="history-item ${isCurrent ? 'current' : ''} ${isMarked ? 'marked' : ''}" data-index="${index}"> <div class="history-item-header"> <span class="history-item-time">${time}</span> <div class="history-item-controls"> ${isCurrent ? `<span class="current-marker">${t('currentPosition')}</span>` : ''} <button class="mark-button ${isMarked ? 'marked' : ''}" data-index="${index}" title="${isMarked ? t('unmarkPosition') : t('markPosition')}"> ${isMarked ? '★' : '☆'} </button> </div> </div> <div class="history-item-location"> ${entry.name} </div> <div class="history-item-coords"> ${formatCoords(entry.lat, entry.lon)} </div> </div> `; }); historyContainer.innerHTML = html; // Event Listener für Klicks auf Historie-Einträge historyContainer.querySelectorAll('.history-item').forEach(item => { item.addEventListener('click', function(e) { if (e.target.classList.contains('mark-button')) { return; // Lass den Button-Handler das übernehmen } const index = parseInt(this.dataset.index); navigateToPosition(navigationHistory[index], index); }); }); // Event Listener für Marker-Buttons historyContainer.querySelectorAll('.mark-button').forEach(button => { button.addEventListener('click', function(e) { e.stopPropagation(); const index = parseInt(this.dataset.index); togglePositionHighlight(index); }); }); } function clearHistory() { // Alle Hervorhebungen entfernen highlightedPositions = {}; navigationHistory = []; currentIndex = -1; updateNavigationButtons(); updateHistoryList(); saveToLocalStorage(); } function saveToLocalStorage() { try { const data = { history: navigationHistory, currentIndex: currentIndex, highlightedPositions: highlightedPositions }; localStorage.setItem('wme-nav-history', JSON.stringify(data)); } catch (error) { console.error('WME Map Nav History: Error saving to localStorage:', error); } } function loadFromLocalStorage() { try { const data = localStorage.getItem('wme-nav-history'); if (data) { const parsed = JSON.parse(data); navigationHistory = parsed.history || []; currentIndex = parsed.currentIndex || -1; highlightedPositions = parsed.highlightedPositions || {}; // Validiere den currentIndex if (currentIndex >= navigationHistory.length) { currentIndex = navigationHistory.length - 1; } } } catch (error) { console.error('WME Map Nav History: Error loading from localStorage:', error); navigationHistory = []; currentIndex = -1; highlightedPositions = {}; } } async function createSidebarTab() { try { const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("wme-nav-history"); tabLabel.innerText = 'NAV'; tabLabel.title = '<h4>Map Navigation History</h4>'; await W.userscripts.waitForElementConnected(tabPane); const styleSheet = document.createElement("style"); styleSheet.textContent = ` .nav-history-button { padding: 8px 15px; cursor: pointer; background: #4a89dc; color: white; border: none; border-radius: 4px; font-weight: 600; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.1); min-width: 100px; display: flex; align-items: center; justify-content: center; gap: 5px; } .nav-history-button:hover { background: #5d9cec; box-shadow: 0 4px 8px rgba(0,0,0,0.15); transform: translateY(-1px); } .nav-history-button:active { transform: translateY(1px); box-shadow: 0 1px 2px rgba(0,0,0,0.1); } .nav-history-button:disabled { background: #b5b5b5; cursor: not-allowed; transform: none; box-shadow: none; } .nav-history-container { background: #f8f9fa; border-radius: 8px; padding: 20px; margin: 10px 0; box-shadow: 0 2px 6px rgba(0,0,0,0.05); } .clear-history-button { background: #dc3545; padding: 6px 12px; font-size: 12px; min-width: auto; } .clear-history-button:hover { background: #c82333; } .history-section { margin-top: 20px; background: #fff; border-radius: 6px; border: 1px solid #e1e4e8; } .history-header { padding: 15px; border-bottom: 1px solid #e1e4e8; display: flex; justify-content: space-between; align-items: center; background: #f8f9fa; border-radius: 6px 6px 0 0; } .history-controls { display: flex; gap: 10px; align-items: center; } .history-list { max-height: 300px; overflow-y: auto; } .history-item { padding: 12px 15px; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background-color 0.2s ease; position: relative; } .history-item:hover { background-color: #f8f9fa; } .history-item.current { background-color: #e8f4fd; border-left: 4px solid #4a89dc; } .history-item.marked { border-left: 4px solid #ffd700; background-color: #fffbf0; } .history-item.current.marked { border-left: 4px solid #4a89dc; background: linear-gradient(to right, #e8f4fd, #fffbf0); } .history-item:last-child { border-bottom: none; } .history-item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; } .history-item-controls { display: flex; align-items: center; gap: 8px; } .history-item-time { font-size: 12px; color: #666; font-weight: 600; } .current-marker { font-size: 10px; background: #4a89dc; color: white; padding: 2px 6px; border-radius: 10px; font-weight: 600; } .mark-button { background: none; border: none; font-size: 16px; cursor: pointer; padding: 2px 4px; border-radius: 3px; transition: all 0.2s ease; color: #ccc; } .mark-button:hover { background: rgba(0,0,0,0.1); color: #ffd700; } .mark-button.marked { color: #ffd700; } .history-item-location { font-weight: 600; color: #2c3e50; margin-bottom: 3px; font-size: 13px; } .history-item-coords { font-size: 11px; color: #7f8c8d; font-family: monospace; } .no-history { padding: 20px; text-align: center; color: #666; font-style: italic; } `; document.head.appendChild(styleSheet); tabPane.innerHTML = ` <div class="nav-history-container"> <p style="margin-top: 0; color: #2c3e50;"><h4>WME Map Nav History</h4></p> <p style="color: #4CAF50; display: flex; align-items: center; gap: 5px;"> <span style="font-size: 18px;">✓</span> ${t('scriptActive')} </p> <div style="display: flex; gap: 15px; margin: 20px 0;"> <button id="nav-history-back" class="nav-history-button"> <span style="font-size: 16px;">⬅️</span> ${t('back')} </button> <button id="nav-history-forward" class="nav-history-button"> ${t('forward')} <span style="font-size: 16px;">➡️</span> </button> </div> <div class="history-section"> <div class="history-header"> <strong>${t('history')}</strong> <div class="history-controls"> <button id="clear-history" class="nav-history-button clear-history-button"> ${t('clearHistory')} </button> </div> </div> <div id="nav-history-list" class="history-list"> <div class="no-history">${t('noHistory')}</div> </div> </div> <hr style="border: none; height: 1px; background: #e1e4e8; margin: 15px 0;"> <div style="background: #fff; padding: 15px; border-radius: 6px; border: 1px solid #e1e4e8;"> <b style="margin-top: 0; color: #2c3e50;">${t('controls')}:</b> <ul style="padding-left: 20px; color: #4a5568;"> <li><strong>Alt + ←</strong> ${t('previousPosition')}</li> <li><strong>Alt + →</strong> ${t('nextPosition')}</li> <li><strong>Click</strong> auf Historie-Eintrag für direkten Sprung</li> <li><strong>★ Button</strong> ${t('markPosition')}</li> </ul> <b style="margin-top: 15px; color: #2c3e50;">${t('hints')}:</b> <ul style="padding-left: 20px; color: #4a5568;"> <li>${t('historyLimit')}</li> <li>${t('autoSave')}</li> <li>${t('autoSaveDescription')}</li> <li>Markierte Positionen werden in der Liste hervorgehoben</li> <li>Hervorhebungen bleiben auch nach Neustart erhalten</li> </ul> </div> </div> `; const backBtn = tabPane.querySelector('#nav-history-back'); const forwardBtn = tabPane.querySelector('#nav-history-forward'); const clearBtn = tabPane.querySelector('#clear-history'); backBtn.addEventListener('click', navigateBack); forwardBtn.addEventListener('click', navigateForward); clearBtn.addEventListener('click', () => { if (confirm(t('confirmClearHistory'))) { clearHistory(); } }); updateNavigationButtons(); updateHistoryList(); return true; } catch (error) { console.error('WME Map Nav History: Error creating sidebar tab:', error); return false; } } function initializeScript() { if (isInitialized) return; try { // Lade gespeicherten Verlauf loadFromLocalStorage(); W.map.events.register('moveend', null, saveCurrentPosition); document.addEventListener('keydown', handleKeyDown); createSidebarTab(); isInitialized = true; saveCurrentPosition(); console.log('WME Map Nav History Extended: Successfully initialized'); } catch (error) { console.error('WME Map Nav History: Initialization error:', error); isInitialized = false; } } if (W?.userscripts?.state.isInitialized) { initializeScript(); } else { document.addEventListener("wme-initialized", initializeScript, { once: true, }); } })();