您需要先安装一个扩展,例如 篡改猴、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 // @description:de Ermöglicht die Navigation durch die Kartenhistorie // @namespace https://greasyfork.org/de/users/863740-horst-wittlich // @version 2025.08.15 // @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 // @description Navigate through map history using Alt + arrow keys, mouse control, clickable history sidebar, and location highlighting // ==/UserScript== /* global W, OpenLayers */ (function() { 'use strict'; console.log('WME Nav History Ultimate: Starting...'); // Global variables - keeping Ultimate features but using Original API let navigationHistory = []; let currentIndex = -1; let isInitialized = false; let lastSaveTime = 0; let bookmarks = []; let workAreas = []; let tags = []; let sessionStartTime = Date.now(); let distanceTracker = { totalDistance: 0, sessionDistance: 0 }; let settings = { autoSave: true, trackingMode: false, smartNotifications: true, maxHistoryEntries: 100, saveInterval: 2000, sortOrder: 'newest' // 'newest' oder 'oldest' }; let currentTab = 'history'; let searchQuery = ''; let highlightedPositions = {}; // Core utility functions function calculateDistance(lat1, lon1, lat2, lon2) { const R = 6371; const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon/2) * Math.sin(dLon/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; } function formatDistance(km) { if (km < 1) return Math.round(km * 1000) + 'm'; return km.toFixed(2) + 'km'; } function formatCoords(lat, lon) { return lat.toFixed(5) + ', ' + lon.toFixed(5); } function formatTime(timestamp) { return new Date(timestamp).toLocaleTimeString(); } function formatDate(timestamp) { return new Date(timestamp).toLocaleDateString(); } function formatSessionTime(milliseconds) { const seconds = Math.floor(milliseconds / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) return hours + 'h ' + (minutes % 60) + 'm'; if (minutes > 0) return minutes + 'm ' + (seconds % 60) + 's'; return seconds + 's'; } function generateLocationName(lat, lon, zoom, customName = null) { if (customName) return customName; const workArea = workAreas.find(area => lat >= area.bounds.south && lat <= area.bounds.north && lon >= area.bounds.west && lon <= area.bounds.east ); if (workArea) return workArea.name + ' - Zoom ' + zoom; return 'Zoom ' + zoom + ' - ' + formatCoords(lat, lon); } function showNotification(message, type = 'info', duration = 3000) { if (!settings.smartNotifications) return; console.log('WME Nav History: ' + message); const notification = document.createElement('div'); notification.textContent = message; notification.style.cssText = 'position:fixed;top:20px;right:20px;padding:12px 20px;border-radius:6px;color:white;font-weight:600;z-index:10000;box-shadow:0 4px 12px rgba(0,0,0,0.3);opacity:0;transform:translateX(100%);transition:all 0.3s ease;background:' + ({ info: '#4a89dc', success: '#5cb85c', warning: '#f0ad4e', error: '#d9534f' }[type] || '#4a89dc'); document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '1'; notification.style.transform = 'translateX(0)'; }, 100); setTimeout(() => { notification.style.opacity = '0'; notification.style.transform = 'translateX(100%)'; setTimeout(() => { if (notification.parentNode) notification.parentNode.removeChild(notification); }, 300); }, duration); } // FIXED: Using EXACT same functions as working original function saveCurrentPosition() { if (!isInitialized || !W.map) return; const now = Date.now(); // Use trackingMode setting but keep Original timing logic if (now - lastSaveTime < (settings.trackingMode ? 1000 : settings.saveInterval)) 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; } // Calculate distance for Ultimate features if (lastEntry) { const distance = calculateDistance(lastEntry.lat, lastEntry.lon, center.lat, center.lon); distanceTracker.totalDistance += distance; distanceTracker.sessionDistance += distance; } // Entferne alle Einträge nach dem aktuellen Index (für Verzweigungen) navigationHistory = navigationHistory.slice(0, currentIndex + 1); const newEntry = { id: Date.now(), lat: center.lat, lon: center.lon, zoom: zoom, timestamp: now, name: generateLocationName(center.lat, center.lon, zoom), marked: false, starred: false, tags: [], notes: '', duration: lastEntry ? now - lastEntry.timestamp : 0 }; navigationHistory.push(newEntry); currentIndex++; lastSaveTime = now; // Begrenze auf Einstellungen aber keep Original logic if (navigationHistory.length > settings.maxHistoryEntries) { const removedEntry = navigationHistory.shift(); currentIndex--; // Update highlighted positions for Ultimate features if (removedEntry.marked && highlightedPositions[0]) { delete highlightedPositions[0]; } const newHighlightedPositions = {}; Object.keys(highlightedPositions).forEach(oldIndex => { const newIndex = parseInt(oldIndex) - 1; if (newIndex >= 0) { newHighlightedPositions[newIndex] = highlightedPositions[oldIndex]; } }); highlightedPositions = newHighlightedPositions; } updateNavigationButtons(); updateHistoryList(); saveToStorage(); } // FIXED: Using EXACT same navigation as working original function navigateToPosition(position, index) { if (!position || !isInitialized || !W.map) return; try { // EXACT same method as working original const lonlat = new OpenLayers.LonLat(position.lon, position.lat); W.map.setCenter(lonlat, position.zoom); if (typeof index !== 'undefined') { currentIndex = index; updateNavigationButtons(); updateHistoryList(); } showNotification('Navigiert zu: ' + position.name, 'success'); } catch (error) { console.error('WME Map Nav History: Navigation error:', error); showNotification('Navigationsfehler: ' + error.message, 'error'); } } // FIXED: Using same key handling as original function handleKeyDown(e) { if (!isInitialized) return; // Don't interfere with input fields if (document.activeElement.tagName.toLowerCase() === 'input' || document.activeElement.tagName.toLowerCase() === 'textarea') { return; } if (e.altKey && e.keyCode === 37) { // Alt + Left navigateBack(); e.preventDefault(); } else if (e.altKey && e.keyCode === 39) { // Alt + Right navigateForward(); e.preventDefault(); } else if (e.ctrlKey && e.keyCode === 66) { // Ctrl + B addBookmarkCurrentPosition(); e.preventDefault(); } else if (e.ctrlKey && e.keyCode === 72) { // Ctrl + H switchTab('history'); e.preventDefault(); } else if (e.ctrlKey && e.shiftKey && e.keyCode === 83) { // Ctrl + Shift + S exportData(); e.preventDefault(); } } function navigateBack() { if (currentIndex > 0) { currentIndex--; navigateToPosition(navigationHistory[currentIndex]); updateNavigationButtons(); updateHistoryList(); showNotification('Zurück zu: ' + navigationHistory[currentIndex].name, 'info'); } else { showNotification('Bereits am Anfang des Verlaufs', 'warning'); } } function navigateForward() { if (currentIndex < navigationHistory.length - 1) { currentIndex++; navigateToPosition(navigationHistory[currentIndex]); updateNavigationButtons(); updateHistoryList(); showNotification('Vorwärts zu: ' + navigationHistory[currentIndex].name, 'info'); } else { showNotification('Bereits am Ende des Verlaufs', 'warning'); } } 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'; } // Update status display for Ultimate features const statusElement = document.getElementById('nav-status'); if (statusElement) { if (navigationHistory.length > 0) { statusElement.textContent = 'Position ' + (currentIndex + 1) + ' von ' + navigationHistory.length; } else { statusElement.textContent = 'Kein Verlauf vorhanden'; } } } // Position highlighting from original - FIXED function togglePositionHighlight(index) { const position = navigationHistory[index]; if (!position) return; if (highlightedPositions[index]) { delete highlightedPositions[index]; position.marked = false; } else { highlightedPositions[index] = true; position.marked = true; } updateHistoryList(); saveToStorage(); } // FIXED: History list update using exact original logic function updateHistoryList() { const historyContainer = document.getElementById('nav-history-list'); if (!historyContainer) return; // Handle search and filtering let filteredHistory = searchQuery ? navigationHistory.filter(entry => entry.name.toLowerCase().includes(searchQuery.toLowerCase()) || formatCoords(entry.lat, entry.lon).includes(searchQuery) ) : [...navigationHistory]; // Apply sorting if (settings.sortOrder === 'oldest') { filteredHistory.sort((a, b) => a.timestamp - b.timestamp); } else { filteredHistory.sort((a, b) => b.timestamp - a.timestamp); } if (filteredHistory.length === 0) { historyContainer.innerHTML = '<div class="no-history">' + (searchQuery ? 'Keine Suchergebnisse gefunden' : 'Kein Verlauf verfügbar') + '</div>'; return; } let html = ''; filteredHistory.forEach((entry) => { const actualIndex = navigationHistory.indexOf(entry); const isCurrent = actualIndex === currentIndex; const isMarked = entry.marked || false; const time = formatTime(entry.timestamp); html += ` <div class="history-item ${isCurrent ? 'current' : ''} ${isMarked ? 'marked' : ''}" data-index="${actualIndex}"> <div class="history-item-header"> <span class="history-item-time">${time}</span> <div class="history-item-controls"> ${isCurrent ? '<span class="current-marker">Aktuell</span>' : ''} <button class="mark-button ${isMarked ? 'marked' : ''}" data-index="${actualIndex}" title="${isMarked ? 'Position entfernen' : 'Position markieren'}"> ${isMarked ? '★' : '☆'} </button> <button class="bookmark-button" data-index="${actualIndex}" title="Als Lesezeichen speichern"> 🔖 </button> </div> </div> <div class="history-item-location"> ${entry.name} </div> <div class="history-item-coords"> ${formatCoords(entry.lat, entry.lon)} </div> ${entry.duration ? '<div class="history-item-duration">Verweildauer: ' + formatSessionTime(entry.duration) + '</div>' : ''} </div> `; }); historyContainer.innerHTML = html; // FIXED: Event Listener - exactly like original historyContainer.querySelectorAll('.history-item').forEach(item => { item.addEventListener('click', function(e) { if (e.target.classList.contains('mark-button') || e.target.classList.contains('bookmark-button')) { return; } const index = parseInt(this.dataset.index); navigateToPosition(navigationHistory[index], index); }); }); // FIXED: Marker buttons - exactly like original historyContainer.querySelectorAll('.mark-button').forEach(button => { button.addEventListener('click', function(e) { e.stopPropagation(); const index = parseInt(this.dataset.index); togglePositionHighlight(index); }); }); // Event Listener für Bookmark-Buttons historyContainer.querySelectorAll('.bookmark-button').forEach(button => { button.addEventListener('click', function(e) { e.stopPropagation(); const index = parseInt(this.dataset.index); const entry = navigationHistory[index]; if (entry) { const name = prompt('Name für Lesezeichen:', entry.name); if (name) addBookmark(name, entry.lat, entry.lon, entry.zoom); } }); }); } // Bookmark management - Ultimate features with Original API function addBookmark(name, lat, lon, zoom, description = '', tags = []) { const bookmark = { id: Date.now(), name: name || generateLocationName(lat, lon, zoom), lat: lat, lon: lon, zoom: zoom, timestamp: Date.now(), description: description, tags: tags, visits: 0, lastVisited: null }; bookmarks.push(bookmark); saveToStorage(); updateUI(); showNotification('Lesezeichen "' + bookmark.name + '" hinzugefügt', 'success'); return bookmark; } function addBookmarkCurrentPosition() { if (!W || !W.map) return; try { const center = W.map.getCenter(); const zoom = W.map.getZoom(); const name = prompt('Name für Lesezeichen:'); if (name) { const description = prompt('Beschreibung (optional):') || ''; addBookmark(name, center.lat, center.lon, zoom, description); } } catch (error) { console.error('Error adding bookmark:', error); showNotification('Fehler beim Hinzufügen des Lesezeichens: ' + error.message, 'error'); } } function removeBookmark(id) { bookmarks = bookmarks.filter(bookmark => bookmark.id !== id); saveToStorage(); updateUI(); showNotification('Lesezeichen entfernt', 'info'); } function navigateToBookmark(bookmark) { bookmark.visits++; bookmark.lastVisited = Date.now(); navigateToPosition(bookmark); saveToStorage(); updateUI(); } // Work area management function addWorkArea() { if (!W || !W.map) return; try { const center = W.map.getCenter(); const zoom = W.map.getZoom(); const name = prompt('Name für Arbeitsbereich:'); if (!name) return; const offset = 0.01 / Math.pow(2, zoom - 10); const workArea = { id: Date.now(), name: name, bounds: { north: center.lat + offset, south: center.lat - offset, east: center.lon + offset, west: center.lon - offset }, created: Date.now(), visits: 0, color: '#' + Math.floor(Math.random()*16777215).toString(16) }; workAreas.push(workArea); saveToStorage(); updateUI(); showNotification('Arbeitsbereich "' + name + '" hinzugefügt', 'success'); } catch (error) { console.error('Error adding work area:', error); } } function removeWorkArea(id) { workAreas = workAreas.filter(area => area.id !== id); saveToStorage(); updateUI(); showNotification('Arbeitsbereich entfernt', 'info'); } function navigateToWorkArea(workArea) { const centerLat = (workArea.bounds.north + workArea.bounds.south) / 2; const centerLon = (workArea.bounds.east + workArea.bounds.west) / 2; try { // EXACT same method as working original const lonlat = new OpenLayers.LonLat(centerLon, centerLat); W.map.setCenter(lonlat, 15); workArea.visits++; saveToStorage(); updateUI(); showNotification('Navigiert zu Arbeitsbereich: ' + workArea.name, 'info'); } catch (error) { console.error('Error navigating to work area:', error); showNotification('Fehler beim Navigieren zum Arbeitsbereich: ' + error.message, 'error'); } } // Data management - Ultimate features function saveToStorage() { try { const data = { history: navigationHistory, currentIndex: currentIndex, bookmarks: bookmarks, workAreas: workAreas, tags: tags, distanceTracker: distanceTracker, settings: settings, highlightedPositions: highlightedPositions, lastSave: Date.now() }; localStorage.setItem('wme-nav-history-ultimate', JSON.stringify(data)); } catch (error) { console.error('WME Nav History: Storage error:', error); } } function loadFromStorage() { try { const data = localStorage.getItem('wme-nav-history-ultimate'); if (data) { const parsed = JSON.parse(data); navigationHistory = parsed.history || []; currentIndex = parsed.currentIndex || -1; bookmarks = parsed.bookmarks || []; workAreas = parsed.workAreas || []; tags = parsed.tags || []; distanceTracker = parsed.distanceTracker || { totalDistance: 0, sessionDistance: 0 }; settings = Object.assign(settings, parsed.settings || {}); highlightedPositions = parsed.highlightedPositions || {}; if (currentIndex >= navigationHistory.length) { currentIndex = navigationHistory.length - 1; } } } catch (error) { console.error('WME Nav History: Load error:', error); } } function clearHistory() { if (confirm('Verlauf wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) { highlightedPositions = {}; navigationHistory = []; currentIndex = -1; distanceTracker.sessionDistance = 0; updateNavigationButtons(); updateHistoryList(); saveToStorage(); showNotification('Verlauf gelöscht', 'info'); } } function exportData() { const exportData = { navigationHistory: navigationHistory, bookmarks: bookmarks, workAreas: workAreas, tags: tags, distanceTracker: distanceTracker, settings: settings, exportDate: new Date().toISOString(), version: '2025.08.13-Ultimate-Fixed-History' }; const dataStr = JSON.stringify(exportData, null, 2); const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); const linkElement = document.createElement('a'); linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', 'wme-nav-history-' + new Date().toISOString().split('T')[0] + '.json'); linkElement.click(); showNotification('Daten exportiert: ' + navigationHistory.length + ' Positionen, ' + bookmarks.length + ' Lesezeichen', 'success'); } function importData() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.style.display = 'none'; input.onchange = function(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { try { const importedData = JSON.parse(e.target.result); if (!importedData || typeof importedData !== 'object') { throw new Error('Ungültige Dateistruktur'); } const merge = confirm('Möchten Sie die Daten zusammenführen (OK) oder komplett ersetzen (Abbrechen)?'); if (merge) { if (importedData.navigationHistory && Array.isArray(importedData.navigationHistory)) { navigationHistory = [...navigationHistory, ...importedData.navigationHistory]; } if (importedData.bookmarks && Array.isArray(importedData.bookmarks)) { bookmarks = [...bookmarks, ...importedData.bookmarks]; } if (importedData.workAreas && Array.isArray(importedData.workAreas)) { workAreas = [...workAreas, ...importedData.workAreas]; } if (importedData.tags && Array.isArray(importedData.tags)) { tags = [...new Set([...tags, ...importedData.tags])]; } showNotification('Daten erfolgreich zusammengeführt', 'success'); } else { navigationHistory = importedData.navigationHistory || []; bookmarks = importedData.bookmarks || []; workAreas = importedData.workAreas || []; tags = importedData.tags || []; if (importedData.distanceTracker) { distanceTracker.totalDistance = importedData.distanceTracker.totalDistance || 0; } if (importedData.settings) { settings = Object.assign(settings, importedData.settings); } showNotification('Daten erfolgreich ersetzt', 'success'); } currentIndex = Math.max(0, navigationHistory.length - 1); navigationHistory = navigationHistory.filter(entry => entry && typeof entry.lat === 'number' && typeof entry.lon === 'number' ); updateUI(); saveToStorage(); showNotification('Import abgeschlossen: ' + navigationHistory.length + ' Positionen, ' + bookmarks.length + ' Lesezeichen', 'success'); } catch (error) { console.error('Import error:', error); showNotification('Fehler beim Importieren: ' + error.message, 'error'); } }; reader.readAsText(file); }; document.body.appendChild(input); input.click(); document.body.removeChild(input); } // UI Updates for Ultimate features function updateUI() { updateNavigationButtons(); updateContent(); } function updateContent() { const contentArea = document.getElementById('nav-content-area'); if (!contentArea) return; while (contentArea.firstChild) { contentArea.removeChild(contentArea.firstChild); } // Set full height for statistics and settings tabs if (currentTab === 'statistics' || currentTab === 'settings') { contentArea.classList.add('full-height'); } else { contentArea.classList.remove('full-height'); } switch(currentTab) { case 'history': renderHistoryContent(contentArea); break; case 'bookmarks': renderBookmarksContent(contentArea); break; case 'areas': renderWorkAreasContent(contentArea); break; case 'statistics': renderStatisticsContent(contentArea); break; case 'settings': renderSettingsContent(contentArea); break; } } function renderHistoryContent(container) { // Clear container first while (container.firstChild) { container.removeChild(container.firstChild); } // Create history list container if it doesn't exist let historyList = document.getElementById('nav-history-list'); if (!historyList) { historyList = document.createElement('div'); historyList.id = 'nav-history-list'; historyList.className = 'history-list'; historyList.style.cssText = 'max-height: 300px; overflow-y: auto;'; container.appendChild(historyList); } else { container.appendChild(historyList); } // Update the history list content updateHistoryList(); } function renderBookmarksContent(container) { if (bookmarks.length === 0) { const noBookmarks = document.createElement('div'); noBookmarks.textContent = 'Keine Lesezeichen vorhanden'; noBookmarks.style.cssText = 'padding:20px;text-align:center;color:#666;font-style:italic;'; container.appendChild(noBookmarks); return; } bookmarks.forEach(bookmark => { const item = document.createElement('div'); item.style.cssText = 'padding:12px;border-bottom:1px solid #eee;cursor:pointer;transition:background 0.2s;border-left:3px solid #28a745;'; const header = document.createElement('div'); header.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:5px;'; const name = document.createElement('span'); name.textContent = bookmark.name; name.style.cssText = 'font-weight:600;color:#2c3e50;'; header.appendChild(name); const deleteBtn = document.createElement('button'); deleteBtn.textContent = '🗑️'; deleteBtn.style.cssText = 'background:none;border:none;cursor:pointer;font-size:12px;'; deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); if (confirm('Lesezeichen "' + bookmark.name + '" wirklich löschen?')) { removeBookmark(bookmark.id); } }); header.appendChild(deleteBtn); item.appendChild(header); const details = document.createElement('div'); details.style.cssText = 'font-size:11px;color:#666;margin-bottom:3px;'; details.textContent = formatCoords(bookmark.lat, bookmark.lon) + ' | Besuche: ' + bookmark.visits; item.appendChild(details); if (bookmark.description) { const description = document.createElement('div'); description.textContent = bookmark.description; description.style.cssText = 'font-size:12px;color:#666;margin:5px 0;font-style:italic;'; item.appendChild(description); } item.addEventListener('click', () => navigateToBookmark(bookmark)); container.appendChild(item); }); } function renderWorkAreasContent(container) { if (workAreas.length === 0) { const noAreas = document.createElement('div'); noAreas.textContent = 'Keine Arbeitsbereiche definiert'; noAreas.style.cssText = 'padding:20px;text-align:center;color:#666;font-style:italic;'; container.appendChild(noAreas); return; } workAreas.forEach(area => { const item = document.createElement('div'); item.style.cssText = 'padding:12px;border-bottom:1px solid #eee;cursor:pointer;transition:background 0.2s;'; const header = document.createElement('div'); header.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:5px;'; const nameContainer = document.createElement('div'); nameContainer.style.cssText = 'display:flex;align-items:center;gap:8px;'; const colorIndicator = document.createElement('span'); colorIndicator.style.cssText = 'width:12px;height:12px;border-radius:50%;background:' + area.color; nameContainer.appendChild(colorIndicator); const name = document.createElement('span'); name.textContent = area.name; name.style.cssText = 'font-weight:600;color:#2c3e50;'; nameContainer.appendChild(name); header.appendChild(nameContainer); const deleteBtn = document.createElement('button'); deleteBtn.textContent = '🗑️'; deleteBtn.style.cssText = 'background:none;border:none;cursor:pointer;font-size:12px;'; deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); if (confirm('Arbeitsbereich "' + area.name + '" wirklich löschen?')) { removeWorkArea(area.id); } }); header.appendChild(deleteBtn); item.appendChild(header); const details = document.createElement('div'); details.style.cssText = 'font-size:11px;color:#666;'; details.textContent = 'Besuche: ' + area.visits + ' | Erstellt: ' + formatDate(area.created); item.appendChild(details); item.addEventListener('click', () => navigateToWorkArea(area)); container.appendChild(item); }); } function renderStatisticsContent(container) { const sessionTime = Date.now() - sessionStartTime; const avgZoom = navigationHistory.length > 0 ? navigationHistory.reduce((sum, entry) => sum + entry.zoom, 0) / navigationHistory.length : 0; // Calculate marked positions count const markedCount = navigationHistory.filter(e => e.marked).length; // Get session positions (positions added this session) const sessionPositions = navigationHistory.filter(entry => entry.timestamp >= sessionStartTime ).length; const statsGrid = document.createElement('div'); statsGrid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fit, minmax(120px, 1fr));gap:8px;padding:10px;width:100%;'; const stats = [ { value: navigationHistory.length.toString(), label: 'Positionen' }, { value: bookmarks.length.toString(), label: 'Lesezeichen' }, { value: markedCount.toString(), label: 'Markiert' }, { value: sessionPositions.toString(), label: 'Session Pos.' }, { value: formatDistance(distanceTracker.totalDistance), label: 'Gesamtdistanz' }, { value: formatDistance(distanceTracker.sessionDistance), label: 'Session-Distanz' }, { value: formatSessionTime(sessionTime), label: 'Session-Zeit' }, { value: avgZoom.toFixed(1), label: 'Ø Zoom' } ]; stats.forEach(stat => { const statItem = document.createElement('div'); statItem.style.cssText = 'text-align:center;padding:8px;background:#f8f9fa;border-radius:4px;border:1px solid #e9ecef;min-width:0;'; const value = document.createElement('div'); value.textContent = stat.value; value.style.cssText = 'font-size:14px;font-weight:bold;color:#4a89dc;margin-bottom:2px;word-break:break-all;'; statItem.appendChild(value); const label = document.createElement('div'); label.textContent = stat.label; label.style.cssText = 'font-size:9px;color:#666;text-transform:uppercase;letter-spacing:0.3px;line-height:1.1;word-break:break-word;'; statItem.appendChild(label); statsGrid.appendChild(statItem); }); container.appendChild(statsGrid); } function renderSettingsContent(container) { const settingsContainer = document.createElement('div'); settingsContainer.style.cssText = 'padding:10px;'; // Auto Save Setting const autoSaveContainer = document.createElement('div'); autoSaveContainer.style.cssText = 'margin-bottom:15px;padding:12px;background:white;border-radius:6px;border:1px solid #e1e4e8;'; const autoSaveTitle = document.createElement('h4'); autoSaveTitle.textContent = 'Automatisches Speichern'; autoSaveTitle.style.cssText = 'margin:0 0 8px 0;color:#2c3e50;font-size:14px;'; autoSaveContainer.appendChild(autoSaveTitle); const autoSaveLabel = document.createElement('label'); autoSaveLabel.style.cssText = 'display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;'; const autoSaveCheckbox = document.createElement('input'); autoSaveCheckbox.type = 'checkbox'; autoSaveCheckbox.checked = settings.autoSave; autoSaveCheckbox.addEventListener('change', () => { settings.autoSave = autoSaveCheckbox.checked; saveToStorage(); showNotification('Automatisches Speichern ' + (settings.autoSave ? 'aktiviert' : 'deaktiviert'), 'info'); }); autoSaveLabel.appendChild(autoSaveCheckbox); const autoSaveText = document.createElement('span'); autoSaveText.textContent = 'Positionen automatisch speichern'; autoSaveLabel.appendChild(autoSaveText); autoSaveContainer.appendChild(autoSaveLabel); settingsContainer.appendChild(autoSaveContainer); // Tracking Mode Setting const trackingContainer = document.createElement('div'); trackingContainer.style.cssText = 'margin-bottom:15px;padding:12px;background:white;border-radius:6px;border:1px solid #e1e4e8;'; const trackingTitle = document.createElement('h4'); trackingTitle.textContent = 'Tracking-Modus'; trackingTitle.style.cssText = 'margin:0 0 8px 0;color:#2c3e50;font-size:14px;'; trackingContainer.appendChild(trackingTitle); const trackingLabel = document.createElement('label'); trackingLabel.style.cssText = 'display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;'; const trackingCheckbox = document.createElement('input'); trackingCheckbox.type = 'checkbox'; trackingCheckbox.checked = settings.trackingMode; trackingCheckbox.addEventListener('change', () => { settings.trackingMode = trackingCheckbox.checked; saveToStorage(); showNotification('Tracking-Modus ' + (settings.trackingMode ? 'aktiviert' : 'deaktiviert'), 'info'); }); trackingLabel.appendChild(trackingCheckbox); const trackingText = document.createElement('span'); trackingText.textContent = 'Häufigere Positionsspeicherung (1s statt 2s)'; trackingLabel.appendChild(trackingText); trackingContainer.appendChild(trackingLabel); settingsContainer.appendChild(trackingContainer); // Notifications Setting const notificationsContainer = document.createElement('div'); notificationsContainer.style.cssText = 'margin-bottom:15px;padding:12px;background:white;border-radius:6px;border:1px solid #e1e4e8;'; const notificationsTitle = document.createElement('h4'); notificationsTitle.textContent = 'Benachrichtigungen'; notificationsTitle.style.cssText = 'margin:0 0 8px 0;color:#2c3e50;font-size:14px;'; notificationsContainer.appendChild(notificationsTitle); const notificationsLabel = document.createElement('label'); notificationsLabel.style.cssText = 'display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;'; const notificationsCheckbox = document.createElement('input'); notificationsCheckbox.type = 'checkbox'; notificationsCheckbox.checked = settings.smartNotifications; notificationsCheckbox.addEventListener('change', () => { settings.smartNotifications = notificationsCheckbox.checked; saveToStorage(); showNotification('Popup-Benachrichtigungen ' + (settings.smartNotifications ? 'aktiviert' : 'deaktiviert'), 'info'); }); notificationsLabel.appendChild(notificationsCheckbox); const notificationsText = document.createElement('span'); notificationsText.textContent = 'Popup-Meldungen oben rechts anzeigen'; notificationsLabel.appendChild(notificationsText); notificationsContainer.appendChild(notificationsLabel); settingsContainer.appendChild(notificationsContainer); // Sort Order Setting const sortContainer = document.createElement('div'); sortContainer.style.cssText = 'margin-bottom:15px;padding:12px;background:white;border-radius:6px;border:1px solid #e1e4e8;'; const sortTitle = document.createElement('h4'); sortTitle.textContent = 'Verlauf Sortierung'; sortTitle.style.cssText = 'margin:0 0 8px 0;color:#2c3e50;font-size:14px;'; sortContainer.appendChild(sortTitle); const sortSelect = document.createElement('select'); sortSelect.style.cssText = 'width:100%;padding:6px;border:1px solid #e1e4e8;border-radius:4px;font-size:12px;'; sortSelect.innerHTML = ` <option value="newest" ${settings.sortOrder === 'newest' ? 'selected' : ''}>Neueste zuerst</option> <option value="oldest" ${settings.sortOrder === 'oldest' ? 'selected' : ''}>Älteste zuerst</option> `; sortSelect.addEventListener('change', () => { settings.sortOrder = sortSelect.value; saveToStorage(); updateUI(); showNotification('Sortierung geändert auf: ' + (settings.sortOrder === 'newest' ? 'Neueste zuerst' : 'Älteste zuerst'), 'info'); }); sortContainer.appendChild(sortSelect); settingsContainer.appendChild(sortContainer); // Data Management const dataContainer = document.createElement('div'); dataContainer.style.cssText = 'margin-bottom:15px;padding:12px;background:white;border-radius:6px;border:1px solid #e1e4e8;'; const dataTitle = document.createElement('h4'); dataTitle.textContent = 'Datenmanagement'; dataTitle.style.cssText = 'margin:0 0 10px 0;color:#2c3e50;font-size:14px;'; dataContainer.appendChild(dataTitle); const buttonGrid = document.createElement('div'); buttonGrid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px;'; const exportBtn = document.createElement('button'); exportBtn.className = 'nav-history-button'; exportBtn.innerHTML = '📤<br>Export'; exportBtn.style.cssText = 'min-height:40px;font-size:11px;padding:6px;'; exportBtn.addEventListener('click', exportData); buttonGrid.appendChild(exportBtn); const importBtn = document.createElement('button'); importBtn.className = 'nav-history-button'; importBtn.innerHTML = '📥<br>Import'; importBtn.style.cssText = 'min-height:40px;background:linear-gradient(135deg, #28a745, #1e7e34);font-size:11px;padding:6px;'; importBtn.addEventListener('click', importData); buttonGrid.appendChild(importBtn); dataContainer.appendChild(buttonGrid); const description = document.createElement('div'); description.style.cssText = 'font-size:10px;color:#666;line-height:1.3;'; description.innerHTML = '<strong>Export:</strong> Speichert alle Daten als JSON-Datei<br><strong>Import:</strong> Lädt gespeicherte Daten (Merge oder Ersetzen)'; dataContainer.appendChild(description); settingsContainer.appendChild(dataContainer); container.appendChild(settingsContainer); } function switchTab(tabName) { currentTab = tabName; // Update tab buttons const tabButtons = document.querySelectorAll('.nav-tab-btn'); tabButtons.forEach(btn => { btn.classList.remove('active'); if (btn.dataset.tab === tabName) { btn.classList.add('active'); } }); // Update header const headerTitle = document.getElementById('nav-header-title'); const headerControls = document.getElementById('nav-header-controls'); if (headerTitle && headerControls) { while (headerControls.firstChild) { headerControls.removeChild(headerControls.firstChild); } switch(tabName) { case 'history': headerTitle.textContent = 'Verlauf'; const clearBtn = document.createElement('button'); clearBtn.className = 'nav-history-button clear-history-button'; clearBtn.textContent = '🗑️ Löschen'; clearBtn.addEventListener('click', clearHistory); headerControls.appendChild(clearBtn); break; case 'bookmarks': headerTitle.textContent = 'Lesezeichen'; const addBookmarkBtn = document.createElement('button'); addBookmarkBtn.className = 'nav-history-button'; addBookmarkBtn.style.cssText = 'padding:4px 8px;font-size:12px;'; addBookmarkBtn.textContent = '➕ Hinzufügen'; addBookmarkBtn.addEventListener('click', addBookmarkCurrentPosition); headerControls.appendChild(addBookmarkBtn); break; case 'statistics': headerTitle.textContent = 'Statistiken'; break; case 'settings': headerTitle.textContent = 'Einstellungen'; break; } } updateUI(); } // FIXED: Using EXACT same sidebar creation as working original async function createSidebarTab() { try { const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("wme-nav-history-ultimate"); tabLabel.innerText = 'NAV+'; tabLabel.title = 'Ultimate Navigation History'; 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.starred { 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, .bookmark-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, .bookmark-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; } .history-item-duration { font-size: 11px; color: #666; margin-top: 3px; } .no-history { padding: 20px; text-align: center; color: #666; font-style: italic; } .nav-tabs { display: flex; background: linear-gradient(135deg, #e9ecef, #dee2e6); border-radius: 8px; margin-bottom: 15px; overflow: hidden; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); } .nav-tab-btn { flex: 1; padding: 8px 4px; background: transparent; border: none; cursor: pointer; font-weight: 600; transition: all 0.3s ease; font-size: 9px; text-align: center; line-height: 1.1; color: #495057; } .nav-tab-btn:hover { background: rgba(74, 137, 220, 0.1); } .nav-tab-btn.active { background: linear-gradient(135deg, #4a89dc, #357abd); color: white; box-shadow: inset 0 2px 4px rgba(0,0,0,0.2); } .nav-search { width: 100%; padding: 10px 15px; border: 2px solid #e9ecef; border-radius: 6px; font-size: 14px; box-sizing: border-box; margin-bottom: 15px; transition: border-color 0.3s ease, box-shadow 0.3s ease; } .nav-search:focus { outline: none; border-color: #4a89dc; box-shadow: 0 0 0 3px rgba(74, 137, 220, 0.1); } .nav-section { background: white; border-radius: 6px; border: 1px solid #e1e4e8; margin: 10px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .nav-header { padding: 12px 15px; background: linear-gradient(135deg, #f1f3f4, #e8eaed); border-bottom: 1px solid #e1e4e8; border-radius: 6px 6px 0 0; display: flex; justify-content: space-between; align-items: center; font-weight: 600; color: #2c3e50; } .nav-content { max-height: 350px; overflow-y: auto; scrollbar-width: thin; } .nav-content.full-height { max-height: none; overflow-y: visible; } .nav-content::-webkit-scrollbar { width: 6px; } .nav-content::-webkit-scrollbar-track { background: #f1f1f1; } .nav-content::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 3px; } `; document.head.appendChild(styleSheet); tabPane.innerHTML = ` <div class="nav-history-container"> <h5 style="margin-top: 0; color: #2c3e50;">WME Navigation History</h5> <p style="color: #4CAF50; display: flex; align-items: center; gap: 5px; margin: 0 0 5px 0;"> <span style="font-size: 18px;">✓</span> Script erfolgreich geladen </p> <div id="nav-status" style="font-size:10px;color:#666;margin:0 0 15px 0;">Initialisierung...</div> <input type="text" class="nav-search" placeholder="Suche in Verlauf, Lesezeichen..." id="nav-search-input"> <div style="display: flex; gap: 15px; margin: 20px 0;"> <button id="nav-history-back" class="nav-history-button"> <span style="font-size: 16px;">⬅️</span> Zurück </button> <button id="nav-history-forward" class="nav-history-button"> Vorwärts <span style="font-size: 16px;">➡️</span> </button> </div> <div class="nav-tabs"> <button class="nav-tab-btn active" data-tab="history">📍<br>Verlauf</button> <button class="nav-tab-btn" data-tab="bookmarks">🔖<br>Bookmarks</button> <button class="nav-tab-btn" data-tab="statistics">📊<br>Stats</button> <button class="nav-tab-btn" data-tab="settings">⚙️<br>Config</button> </div> <div class="nav-section"> <div class="nav-header"> <strong id="nav-header-title">Verlauf</strong> <div id="nav-header-controls" style="display:flex;gap:5px;"> <button class="nav-history-button clear-history-button" id="clear-history"> 🗑️ Löschen </button> </div> </div> <div id="nav-content-area" class="nav-content"> <div id="nav-history-list" class="history-list"> <div class="no-history">Kein Verlauf verfügbar</div> </div> </div> </div> <div style="margin-top:15px;padding:15px;background:white;border-radius:6px;border:1px solid #e1e4e8;"> <strong style="color: #2c3e50;">Steuerung:</strong> <ul style="padding-left: 20px; color: #4a5568; margin: 10px 0;"> <li><strong>Alt + ←</strong> Vorherige Position</li> <li><strong>Alt + →</strong> Nächste Position</li> <li><strong>Klick</strong> auf Historie-Eintrag für direkten Sprung</li> <li><strong>★ Button</strong> Position markieren</li> <li><strong>🔖 Button</strong> Als Lesezeichen speichern</li> </ul> <strong style="color: #2c3e50;">Hinweise:</strong> <ul style="padding-left: 20px; color: #4a5568; margin: 10px 0;"> <li>Die Historie speichert bis zu 100 Positionen</li> <li>Neue Positionen werden beim Verschieben der Karte automatisch gespeichert</li> <li>Verlauf wird automatisch gespeichert und beim nächsten Start geladen</li> <li>Markierte Positionen werden in der Liste hervorgehoben</li> <li>Hervorhebungen bleiben auch nach Neustart erhalten</li> <li>Alle Daten können über Export/Import gesichert werden</li> </ul> <strong style="color: #2c3e50;">Shortcuts:</strong><br> <span style="font-size: 11px; color: #666;"> Alt+←/→ Navigation | Ctrl+B Lesezeichen | Ctrl+H Verlauf | Ctrl+Shift+S Export </span> </div> </div> `; const backBtn = tabPane.querySelector('#nav-history-back'); const forwardBtn = tabPane.querySelector('#nav-history-forward'); const clearBtn = tabPane.querySelector('#clear-history'); const searchInput = tabPane.querySelector('#nav-search-input'); backBtn.addEventListener('click', navigateBack); forwardBtn.addEventListener('click', navigateForward); clearBtn.addEventListener('click', clearHistory); searchInput.addEventListener('input', (e) => { searchQuery = e.target.value; updateUI(); }); // Tab switching tabPane.querySelectorAll('.nav-tab-btn').forEach(btn => { btn.addEventListener('click', () => { switchTab(btn.dataset.tab); }); }); updateNavigationButtons(); updateHistoryList(); showNotification('Navigation History+ Ultimate aktiviert - Verlauf repariert', 'success'); return true; } catch (error) { console.error('WME Map Nav History: Error creating sidebar tab:', error); return false; } } // FIXED: Using EXACT same initialization as working original function initializeScript() { if (isInitialized) return; try { loadFromStorage(); // EXACT same event registration as working original W.map.events.register('moveend', null, saveCurrentPosition); document.addEventListener('keydown', handleKeyDown); createSidebarTab(); isInitialized = true; saveCurrentPosition(); console.log('WME Map Nav History Ultimate: Successfully initialized with fixed history display'); } catch (error) { console.error('WME Map Nav History: Initialization error:', error); isInitialized = false; } } // EXACT same initialization check as working original if (W?.userscripts?.state.isInitialized) { initializeScript(); } else { document.addEventListener("wme-initialized", initializeScript, { once: true, }); } })();