/** * ThrillWiki Map Integration - Master Integration Script * * This module coordinates all map components, handles initialization order, * manages component communication, and provides a unified API */ class MapIntegration { constructor(options = {}) { this.options = { autoInit: true, enableLogging: true, enablePerformanceMonitoring: true, initTimeout: 10000, retryAttempts: 3, components: { maps: true, filters: true, roadtrip: true, geolocation: true, markers: true, htmx: true, mobileTouch: true, darkMode: true }, ...options }; this.components = {}; this.initOrder = [ 'darkMode', 'mobileTouch', 'maps', 'markers', 'filters', 'geolocation', 'htmx', 'roadtrip' ]; this.initialized = false; this.initStartTime = null; this.errors = []; if (this.options.autoInit) { this.init(); } } /** * Initialize all map components */ async init() { this.initStartTime = performance.now(); this.log('Starting map integration initialization...'); try { // Wait for DOM to be ready await this.waitForDOM(); // Initialize components in order await this.initializeComponents(); // Connect components this.connectComponents(); // Setup global event handlers this.setupGlobalHandlers(); // Verify integration this.verifyIntegration(); this.initialized = true; this.logPerformance(); this.log('Map integration initialized successfully'); // Emit ready event this.emitEvent('mapIntegrationReady', { components: Object.keys(this.components), initTime: performance.now() - this.initStartTime }); } catch (error) { this.handleInitError(error); } } /** * Wait for DOM to be ready */ waitForDOM() { return new Promise((resolve) => { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', resolve); } else { resolve(); } }); } /** * Initialize components in the correct order */ async initializeComponents() { for (const componentName of this.initOrder) { if (!this.options.components[componentName]) { this.log(`Skipping ${componentName} (disabled)`); continue; } try { await this.initializeComponent(componentName); this.log(`✓ ${componentName} initialized`); } catch (error) { this.error(`✗ Failed to initialize ${componentName}:`, error); this.errors.push({ component: componentName, error }); } } } /** * Initialize individual component */ async initializeComponent(componentName) { switch (componentName) { case 'darkMode': if (window.DarkModeMaps) { this.components.darkMode = window.darkModeMaps || new DarkModeMaps(); } break; case 'mobileTouch': if (window.MobileTouchSupport) { this.components.mobileTouch = window.mobileTouchSupport || new MobileTouchSupport(); } break; case 'maps': // Look for existing map instances or create new ones if (window.thrillwikiMap) { this.components.maps = window.thrillwikiMap; } else if (window.ThrillWikiMap) { const mapContainer = document.getElementById('map-container'); if (mapContainer) { this.components.maps = new ThrillWikiMap('map-container'); window.thrillwikiMap = this.components.maps; } } break; case 'markers': if (window.MapMarkers && this.components.maps) { this.components.markers = window.mapMarkers || new MapMarkers(this.components.maps); } break; case 'filters': if (window.MapFilters) { const filterForm = document.getElementById('map-filters'); if (filterForm) { this.components.filters = window.mapFilters || new MapFilters('map-filters'); } } break; case 'geolocation': if (window.UserLocation) { this.components.geolocation = window.userLocation || new UserLocation(); } break; case 'htmx': if (window.HTMXMapIntegration && typeof htmx !== 'undefined') { this.components.htmx = window.htmxMapIntegration || new HTMXMapIntegration(); } break; case 'roadtrip': if (window.RoadTripPlanner) { const roadtripContainer = document.getElementById('roadtrip-planner'); if (roadtripContainer) { this.components.roadtrip = window.roadTripPlanner || new RoadTripPlanner('roadtrip-planner'); } } break; } } /** * Connect components together */ connectComponents() { this.log('Connecting components...'); // Connect maps to other components if (this.components.maps) { // Connect to dark mode if (this.components.darkMode) { this.components.darkMode.registerMapInstance(this.components.maps); } // Connect to mobile touch if (this.components.mobileTouch) { this.components.mobileTouch.registerMapInstance(this.components.maps); } // Connect to geolocation if (this.components.geolocation) { this.components.geolocation.connectToMap(this.components.maps); } // Connect to road trip planner if (this.components.roadtrip) { this.components.roadtrip.connectToMap(this.components.maps); } } // Connect filters to other components if (this.components.filters) { // Connect to maps if (this.components.maps) { this.components.filters.connectToMap(this.components.maps); } // Connect to HTMX if (this.components.htmx) { this.components.htmx.connectToFilter(this.components.filters); } } // Connect HTMX to maps if (this.components.htmx && this.components.maps) { this.components.htmx.connectToMap(this.components.maps); } } /** * Setup global event handlers */ setupGlobalHandlers() { // Handle global map events document.addEventListener('mapDataUpdate', (e) => { this.handleMapDataUpdate(e.detail); }); // Handle filter changes document.addEventListener('filterChange', (e) => { this.handleFilterChange(e.detail); }); // Handle theme changes document.addEventListener('themeChanged', (e) => { this.handleThemeChange(e.detail); }); // Handle orientation changes document.addEventListener('orientationChanged', (e) => { this.handleOrientationChange(e.detail); }); // Handle visibility changes for performance document.addEventListener('visibilitychange', () => { this.handleVisibilityChange(); }); // Handle errors window.addEventListener('error', (e) => { if (this.isMapRelatedError(e)) { this.handleGlobalError(e); } }); } /** * Handle map data updates */ handleMapDataUpdate(data) { if (this.components.maps) { this.components.maps.updateMarkers(data); } } /** * Handle filter changes */ handleFilterChange(filters) { if (this.components.maps) { this.components.maps.updateFilters(filters); } } /** * Handle theme changes */ handleThemeChange(themeData) { // All components should already be listening for this // Just log for monitoring this.log(`Theme changed to ${themeData.newTheme}`); } /** * Handle orientation changes */ handleOrientationChange(orientationData) { // Invalidate map sizes after orientation change if (this.components.maps) { setTimeout(() => { this.components.maps.invalidateSize(); }, 300); } } /** * Handle visibility changes */ handleVisibilityChange() { const isHidden = document.hidden; // Pause/resume location watching if (this.components.geolocation) { if (isHidden) { this.components.geolocation.stopWatching(); } else if (this.components.geolocation.options.watchPosition) { this.components.geolocation.startWatching(); } } } /** * Check if error is map-related */ isMapRelatedError(error) { const mapKeywords = ['leaflet', 'map', 'marker', 'tile', 'geolocation', 'htmx']; const errorMessage = error.message ? error.message.toLowerCase() : ''; const errorStack = error.error && error.error.stack ? error.error.stack.toLowerCase() : ''; return mapKeywords.some(keyword => errorMessage.includes(keyword) || errorStack.includes(keyword) ); } /** * Handle global errors */ handleGlobalError(error) { this.error('Global map error:', error); this.errors.push({ type: 'global', error }); // Emit error event this.emitEvent('mapError', { error, timestamp: Date.now() }); } /** * Verify integration is working */ verifyIntegration() { const issues = []; // Check required components if (this.options.components.maps && !this.components.maps) { issues.push('Maps component not initialized'); } // Check component connections if (this.components.maps && this.components.darkMode) { if (!this.components.darkMode.mapInstances.has(this.components.maps)) { issues.push('Maps not connected to dark mode'); } } // Check DOM elements const mapContainer = document.getElementById('map-container'); if (this.components.maps && !mapContainer) { issues.push('Map container not found in DOM'); } if (issues.length > 0) { this.warn('Integration issues found:', issues); } return issues.length === 0; } /** * Handle initialization errors */ handleInitError(error) { this.error('Map integration initialization failed:', error); // Emit error event this.emitEvent('mapIntegrationError', { error, errors: this.errors, timestamp: Date.now() }); // Try to initialize what we can this.attemptPartialInit(); } /** * Attempt partial initialization */ attemptPartialInit() { this.log('Attempting partial initialization...'); // Try to initialize at least the core map if (!this.components.maps && window.ThrillWikiMap) { try { const mapContainer = document.getElementById('map-container'); if (mapContainer) { this.components.maps = new ThrillWikiMap('map-container'); this.log('✓ Core map initialized in fallback mode'); } } catch (error) { this.error('✗ Fallback map initialization failed:', error); } } } /** * Get component by name */ getComponent(name) { return this.components[name] || null; } /** * Get all components */ getAllComponents() { return { ...this.components }; } /** * Check if integration is ready */ isReady() { return this.initialized; } /** * Get initialization status */ getStatus() { return { initialized: this.initialized, components: Object.keys(this.components), errors: this.errors, initTime: this.initStartTime ? performance.now() - this.initStartTime : null }; } /** * Emit custom event */ emitEvent(eventName, detail) { const event = new CustomEvent(eventName, { detail }); document.dispatchEvent(event); } /** * Log performance metrics */ logPerformance() { if (!this.options.enablePerformanceMonitoring) return; const initTime = performance.now() - this.initStartTime; const componentCount = Object.keys(this.components).length; this.log(`Performance: ${initTime.toFixed(2)}ms to initialize ${componentCount} components`); // Send to analytics if available if (typeof gtag !== 'undefined') { gtag('event', 'map_integration_performance', { event_category: 'performance', value: Math.round(initTime), custom_map: { component_count: componentCount, errors: this.errors.length } }); } } /** * Logging methods */ log(message, ...args) { if (this.options.enableLogging) { console.log(`[MapIntegration] ${message}`, ...args); } } warn(message, ...args) { if (this.options.enableLogging) { console.warn(`[MapIntegration] ${message}`, ...args); } } error(message, ...args) { if (this.options.enableLogging) { console.error(`[MapIntegration] ${message}`, ...args); } } /** * Destroy integration */ destroy() { // Destroy all components Object.values(this.components).forEach(component => { if (component && typeof component.destroy === 'function') { try { component.destroy(); } catch (error) { this.error('Error destroying component:', error); } } }); this.components = {}; this.initialized = false; this.log('Map integration destroyed'); } } // Auto-initialize map integration let mapIntegration; document.addEventListener('DOMContentLoaded', function() { // Only initialize if we have map-related elements const hasMapElements = document.querySelector('#map-container, .map-container, [data-map], [data-roadtrip]'); if (hasMapElements) { mapIntegration = new MapIntegration(); window.mapIntegration = mapIntegration; } }); // Global API for external access window.ThrillWikiMaps = { getIntegration: () => mapIntegration, isReady: () => mapIntegration && mapIntegration.isReady(), getComponent: (name) => mapIntegration ? mapIntegration.getComponent(name) : null, getStatus: () => mapIntegration ? mapIntegration.getStatus() : { initialized: false } }; // Export for module systems if (typeof module !== 'undefined' && module.exports) { module.exports = MapIntegration; } else { window.MapIntegration = MapIntegration; }