Greasy Fork镜像 is available in English.

WME Historial de Navegación del Mapa

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

// ==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.dpdns.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,
    });
}

})();
长期地址
遇到问题?请前往 GitHub 提 Issues,或加Q群1031348184

赞助商

Fishcpy

广告

Rainyun

一年攒够 12 元

云驰互联

云驰互联