/** * CarCache Editor for StreetRacer Web Dashboard * Allows VIP users to create CarCaches with checkpoints and quizzes via Mapbox * Uses modal dialogs for checkpoint/quiz editing */ class CarCacheEditor { constructor() { this.map = null; this.isInitialized = false; this.currentTool = 'select'; this.checkpoints = []; // { name, lat, lng, quiz: { question, answers, correctIndex } } this.markers = []; this.currentCacheId = null; this.mapboxToken = 'pk.eyJ1IjoiZnJhbmNvaXNyZWluZXJ0IiwiYSI6ImNtazBja2liYjBvbnozZnM4bWE2OGF0b3UifQ.3k-VptMsIpvD0ys4igFWeg'; console.log('CarCacheEditor constructed'); } reset() { if (this.map) { this.map.remove(); this.map = null; } this.isInitialized = false; this.checkpoints = []; this.markers = []; this.currentCacheId = null; } initialize() { if (this.isInitialized && this.map) { this.map.resize(); return; } // Check VIP access (permanent_vip, subscriber, or unlocked via credits) const authHandler = window.authHandler; const hasAccess = authHandler && ( authHandler.accessLevel === 'permanent_vip' || authHandler.accessLevel === 'subscriber' || authHandler.unlockedFeatures?.['carcache-editor'] ); const gate = document.getElementById('carcache-vip-gate'); const content = document.getElementById('carcache-editor-content'); if (!hasAccess) { gate.style.display = 'block'; content.style.display = 'none'; return; } gate.style.display = 'none'; content.style.display = 'grid'; let attempts = 0; const checkAndInit = () => { attempts++; const container = document.getElementById('carcache-editor-map'); const tabContent = document.getElementById('tab-carcache-editor'); const isTabVisible = tabContent && !tabContent.classList.contains('hidden'); const width = container?.getBoundingClientRect().width || 0; const height = container?.getBoundingClientRect().height || 0; if (container && width > 100 && height > 100 && isTabVisible) { this.initializeMap(); this.setupToolbar(); this.loadMyCaches(); } else if (attempts < 30) { setTimeout(checkAndInit, 150); } else if (container) { container.style.width = '100%'; container.style.height = '500px'; setTimeout(() => { this.initializeMap(); this.setupToolbar(); this.loadMyCaches(); }, 100); } }; setTimeout(checkAndInit, 50); } initializeMap() { const container = document.getElementById('carcache-editor-map'); if (!container) return; try { mapboxgl.accessToken = this.mapboxToken; this.map = new mapboxgl.Map({ container: 'carcache-editor-map', style: 'mapbox://styles/mapbox/dark-v11', center: [10.45, 51.16], zoom: 6, }); this.map.addControl(new mapboxgl.NavigationControl(), 'top-right'); this.map.on('load', () => { this.isInitialized = true; console.log('CarCache editor map loaded'); }); this.map.on('click', (e) => { if (this.currentTool === 'checkpoint') { this.addCheckpoint(e.lngLat.lat, e.lngLat.lng); } }); // Try to center on user's location if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(pos => { this.map.flyTo({ center: [pos.coords.longitude, pos.coords.latitude], zoom: 13 }); }, () => {}); } } catch (err) { console.error('Error initializing CarCache map:', err); } } setupToolbar() { document.querySelectorAll('[data-cctool]').forEach(btn => { btn.addEventListener('click', (e) => { const tool = e.currentTarget.dataset.cctool; this.setTool(tool); }); }); document.getElementById('cc-btn-undo')?.addEventListener('click', () => { if (this.checkpoints.length > 0) { this.removeCheckpoint(this.checkpoints.length - 1); } }); document.getElementById('cc-btn-clear')?.addEventListener('click', () => { if (this.checkpoints.length > 0 && confirm('Alle Checkpoints entfernen?')) { this.clearAll(); } }); document.getElementById('cc-btn-save')?.addEventListener('click', () => { this.saveCache(); }); document.getElementById('cc-btn-new')?.addEventListener('click', () => { this.newCache(); }); } setTool(tool) { this.currentTool = tool; document.querySelectorAll('[data-cctool]').forEach(btn => { btn.classList.toggle('active', btn.dataset.cctool === tool); }); if (this.map) { this.map.getCanvas().style.cursor = tool === 'checkpoint' ? 'crosshair' : ''; } } addCheckpoint(lat, lng) { const idx = this.checkpoints.length; const cp = { name: `Checkpoint ${idx + 1}`, lat, lng, quiz: idx > 0 ? { question: '', answers: ['', '', '', ''], correctIndex: 0 } : null, }; this.checkpoints.push(cp); const el = document.createElement('div'); const isStart = idx === 0; el.style.cssText = `width:30px;height:30px;border-radius:50%;background:${isStart ? '#2ecc71' : '#E040FB'};color:#fff;font-weight:bold;font-size:13px;display:flex;align-items:center;justify-content:center;border:2px solid #fff;cursor:grab;`; el.textContent = idx + 1; const marker = new mapboxgl.Marker({ element: el, draggable: true }) .setLngLat([lng, lat]) .addTo(this.map); marker.on('dragend', () => { const pos = marker.getLngLat(); cp.lat = pos.lat; cp.lng = pos.lng; }); // Click marker to open edit modal el.addEventListener('click', (e) => { e.stopPropagation(); this.openCheckpointModal(idx); }); this.markers.push(marker); this.updateUI(); } removeCheckpoint(idx) { if (this.markers[idx]) { this.markers[idx].remove(); } this.checkpoints.splice(idx, 1); this.markers.splice(idx, 1); // Recreate markers with correct numbers this.markers.forEach(m => m.remove()); this.markers = []; this.checkpoints.forEach((cp, i) => { const el = document.createElement('div'); const isStart = i === 0; el.style.cssText = `width:30px;height:30px;border-radius:50%;background:${isStart ? '#2ecc71' : '#E040FB'};color:#fff;font-weight:bold;font-size:13px;display:flex;align-items:center;justify-content:center;border:2px solid #fff;cursor:grab;`; el.textContent = i + 1; const marker = new mapboxgl.Marker({ element: el, draggable: true }) .setLngLat([cp.lng, cp.lat]) .addTo(this.map); marker.on('dragend', () => { const pos = marker.getLngLat(); cp.lat = pos.lat; cp.lng = pos.lng; }); el.addEventListener('click', (ev) => { ev.stopPropagation(); this.openCheckpointModal(i); }); this.markers.push(marker); if (i === 0) cp.quiz = null; else if (!cp.quiz) cp.quiz = { question: '', answers: ['', '', '', ''], correctIndex: 0 }; }); this.updateUI(); } clearAll() { this.markers.forEach(m => m.remove()); this.markers = []; this.checkpoints = []; this.currentCacheId = null; this.updateUI(); } newCache() { this.clearAll(); document.getElementById('cc-name').value = ''; document.getElementById('cc-description').value = ''; document.getElementById('cc-status').textContent = 'Nicht gespeichert'; this.setTool('checkpoint'); } updateUI() { document.getElementById('cc-checkpoint-count').textContent = this.checkpoints.length; this.renderCheckpointList(); } // Compact sidebar list - click to open modal renderCheckpointList() { const container = document.getElementById('cc-checkpoints-list'); if (!container) return; if (this.checkpoints.length === 0) { container.innerHTML = '