class RekordboxSyncRenderer { constructor() { this.initializeElements(); this.setupEventListeners(); this.setupElectronListeners(); this.loadInitialState(); } /** * Initialize DOM elements */ initializeElements() { // Buttons this.exportEnvBtn = document.getElementById('exportEnvBtn'); // Status elements this.syncStatusElement = document.getElementById('syncStatus'); this.filesSyncedElement = document.getElementById('filesSynced'); this.lastSyncElement = document.getElementById('lastSync'); this.syncDetailsElement = document.getElementById('syncDetails'); this.syncModeElement = document.getElementById('syncMode'); // Activity log this.activityLogElement = document.getElementById('activityLog'); // Configuration elements this.s3EndpointInput = document.getElementById('s3Endpoint'); this.s3AccessKeyInput = document.getElementById('s3AccessKey'); this.s3SecretKeyInput = document.getElementById('s3SecretKey'); this.s3BucketInput = document.getElementById('s3Bucket'); this.s3RegionInput = document.getElementById('s3Region'); this.localPathInput = document.getElementById('localPath'); this.syncIntervalInput = document.getElementById('syncInterval'); // Save button this.saveConfigBtn = document.getElementById('saveConfigBtn'); } /** * Setup event listeners for UI elements */ setupEventListeners() { // Sync control buttons if (this.startBtn) { this.startBtn.addEventListener('click', () => this.startSync()); } if (this.stopBtn) { this.stopBtn.addEventListener('click', () => this.stopSync()); } if (this.exportEnvBtn) { this.exportEnvBtn.addEventListener('click', () => this.exportToEnv()); } // Configuration save button if (this.saveConfigBtn) { this.saveConfigBtn.addEventListener('click', () => this.saveConfiguration()); } // Force sync button const forceSyncBtn = document.getElementById('forceSyncBtn'); if (forceSyncBtn) { forceSyncBtn.addEventListener('click', () => this.forceFullSync()); } // Immediate sync button const immediateSyncBtn = document.getElementById('immediateSyncBtn'); if (immediateSyncBtn) { immediateSyncBtn.addEventListener('click', () => this.triggerImmediateSync()); } } /** * Setup Electron IPC listeners */ setupElectronListeners() { if (!window.electronAPI) { console.error('❌ Electron API not available'); return; } // Sync status updates window.electronAPI.on('sync-status-changed', (status) => { this.updateSyncStatus(status); }); // File change events window.electronAPI.on('file-changed', (event) => { console.log('📁 File changed:', event); this.addActivityLog('info', `File changed: ${event.path}`); }); window.electronAPI.on('file-added', (event) => { console.log('➕ File added:', event); this.addActivityLog('success', `File added: ${event.path}`); }); window.electronAPI.on('file-removed', (event) => { console.log('➖ File removed:', event); this.addActivityLog('info', `File removed: ${event.path}`); }); // Sync operation updates window.electronAPI.on('sync-operation-started', (operation) => { console.log('🔄 Operation started:', operation); this.addActivityLog('info', `Started ${operation.type}: ${operation.s3Key || operation.localPath}`); }); window.electronAPI.on('sync-operation-completed', (operation) => { console.log('✅ Operation completed:', operation); this.addActivityLog('success', `Completed ${operation.type}: ${operation.s3Key || operation.localPath}`); }); window.electronAPI.on('sync-operation-failed', (operation) => { console.log('❌ Operation failed:', operation); this.addActivityLog('error', `Failed ${operation.type}: ${operation.s3Key || operation.localPath} - ${operation.error || 'Unknown error'}`); }); // Sync lifecycle events window.electronAPI.on('sync-started', (type) => { console.log('🚀 Sync started:', type); this.addActivityLog('info', `Sync started: ${type}`); }); window.electronAPI.on('sync-completed', (type) => { console.log('🎉 Sync completed:', type); this.addActivityLog('success', `Sync completed: ${type}`); }); window.electronAPI.on('sync-error', (error) => { console.log('đŸ’Ĩ Sync error:', error); this.addActivityLog('error', `Sync error: ${error.message || 'Unknown error'}`); // Update UI to show error state if (this.syncStatusElement) { this.syncStatusElement.textContent = 'Error - Sync failed'; this.syncStatusElement.className = 'status-value error'; } // Re-enable start button on error if (this.startBtn) this.startBtn.disabled = false; if (this.stopBtn) this.stopBtn.disabled = true; }); // Engine events window.electronAPI.on('sync-engine-started', () => { console.log('✅ Sync engine started'); this.addActivityLog('success', 'Sync engine started'); }); window.electronAPI.on('sync-engine-stopped', () => { console.log('âšī¸ Sync engine stopped'); this.addActivityLog('info', 'Sync engine stopped'); }); // MinIO output events window.electronAPI.on('aws-output', (output) => { console.log('🔍 AWS S3 output received:', output); this.addActivityLog('info', `AWS S3 ${output.direction}: ${output.output}`); }); // File change events window.electronAPI.on('file-changed', (event) => { console.log('📁 File changed:', event); this.addActivityLog('info', `File changed: ${event.path}`); }); } /** * Load initial application state */ async loadInitialState() { try { // Load configuration const config = await window.electronAPI.invoke('config:get'); this.populateConfigurationForm(config); // Load current sync status const status = await window.electronAPI.invoke('sync:get-status'); if (status) { this.updateSyncStatus(status); } } catch (error) { console.error('❌ Failed to load initial state:', error); this.addActivityLog('error', `Failed to load initial state: ${error.message || 'Unknown error'}`); } } /** * Force full sync */ async forceFullSync() { try { this.addActivityLog('info', 'Forcing full sync...'); await window.electronAPI.invoke('sync:force-full'); this.addActivityLog('success', 'Full sync initiated'); } catch (error) { console.error('❌ Failed to force full sync:', error); this.addActivityLog('error', `Failed to force full sync: ${error.message || 'Unknown error'}`); } } /** * Update sync status display */ updateSyncStatus(status) { if (!status) return; // Update main status with more detailed information if (this.syncStatusElement) { if (status.isRunning) { let statusText = 'Running'; // Add current phase information if (status.currentPhase) { statusText += ` - ${status.currentPhase}`; } // Add file counts (cleaner display) if (status.stats && status.stats.totalFilesSynced > 0) { statusText += ` - ${status.stats.totalFilesSynced} local files`; } this.syncStatusElement.textContent = statusText; this.syncStatusElement.className = 'status-value running'; } else { if (status.completedCount > 0 && status.pendingCount === 0 && status.inProgressCount === 0) { this.syncStatusElement.textContent = 'Completed'; this.syncStatusElement.className = 'status-value completed'; } else { this.syncStatusElement.textContent = 'Stopped'; this.syncStatusElement.className = 'status-value stopped'; } if (this.startBtn) this.startBtn.disabled = false; if (this.stopBtn) this.stopBtn.disabled = true; } // Also update the force sync button state const forceSyncBtn = document.getElementById('forceSyncBtn'); if (forceSyncBtn) { forceSyncBtn.disabled = status.isRunning; } } // Update files synced count with more detail if (this.filesSyncedElement) { console.log('🔍 Updating files synced element with:', { actualFileCount: status.actualFileCount, statsTotalFiles: status.stats?.totalFilesSynced }); // Use actual file count from main process if available if (status.actualFileCount !== undefined) { const text = `${status.actualFileCount} files in local folder`; console.log('📝 Setting files synced text to:', text); this.filesSyncedElement.textContent = text; } else if (status.stats && status.stats.totalFilesSynced > 0) { const text = `${status.stats.totalFilesSynced} files in local folder`; console.log('📝 Setting files synced text to:', text); this.filesSyncedElement.textContent = text; } else { console.log('📝 Setting files synced text to: 0 files'); this.filesSyncedElement.textContent = '0 files'; } } else { console.warn('âš ī¸ filesSyncedElement not found!'); } // Update last sync time if (this.lastSyncElement) { if (status.lastSync) { const date = new Date(status.lastSync); this.lastSyncElement.textContent = date.toLocaleString(); } else { this.lastSyncElement.textContent = 'Never'; } } // Update detailed status this.updateDetailedStatus(status); // Update phase and progress if (status.currentPhase && status.progressMessage) { this.addActivityLog('info', `${status.currentPhase}: ${status.progressMessage}`); } // Log progress changes to reduce spam if (status.progress && status.progress.percent > 0) { const currentProgress = status.progress.percent; if (!this.lastLoggedProgress || Math.abs(currentProgress - this.lastLoggedProgress) >= 10) { const progressText = `Progress: ${currentProgress}% - ${status.progress.message || 'Syncing files...'}`; this.addActivityLog('info', progressText); this.lastLoggedProgress = currentProgress; } } // Add file count updates (but only when they change significantly) if (status.stats && status.stats.totalFilesSynced > 0) { const currentFileCount = status.stats.totalFilesSynced; if (!this.lastLoggedFileCount || Math.abs(currentFileCount - this.lastLoggedFileCount) >= 50) { const fileText = `Local files: ${currentFileCount} (${status.stats.filesDownloaded} downloaded, ${status.stats.filesUploaded} uploaded)`; this.addActivityLog('success', fileText); this.lastLoggedFileCount = currentFileCount; } } } /** * Update detailed status display */ updateDetailedStatus(status) { if (!this.syncDetailsElement) return; let detailsHTML = ''; if (status.pendingCount > 0) { detailsHTML += `
📋 ${status.pendingCount} pending
`; } if (status.inProgressCount > 0) { detailsHTML += `
🔄 ${status.inProgressCount} in progress
`; } if (status.completedCount > 0) { detailsHTML += `
✅ ${status.completedCount} completed
`; } if (status.failedCount > 0) { detailsHTML += `
❌ ${status.failedCount} failed
`; } if (status.errors && status.errors.length > 0) { detailsHTML += `
âš ī¸ ${status.errors.length} errors
`; } this.syncDetailsElement.innerHTML = detailsHTML; } /** * Populate configuration form */ populateConfigurationForm(config) { if (!config) return; if (this.s3EndpointInput) this.s3EndpointInput.value = config.s3?.endpoint || ''; if (this.s3AccessKeyInput) this.s3AccessKeyInput.value = config.s3?.accessKeyId || ''; if (this.s3SecretKeyInput) this.s3SecretKeyInput.value = config.s3?.secretAccessKey || ''; if (this.s3BucketInput) this.s3BucketInput.value = config.s3?.bucketName || ''; if (this.s3RegionInput) this.s3RegionInput.value = config.s3?.region || ''; if (this.localPathInput) this.localPathInput.value = config.sync?.localPath || ''; if (this.syncIntervalInput) this.syncIntervalInput.value = config.sync?.syncInterval || 30000; } /** * Save configuration */ async saveConfiguration() { try { const config = { s3: { endpoint: this.s3EndpointInput?.value || '', accessKeyId: this.s3AccessKeyInput?.value || '', secretAccessKey: this.s3SecretKeyInput?.value || '', bucketName: this.s3BucketInput?.value || '', region: this.s3RegionInput?.value || '', }, sync: { localPath: this.localPathInput?.value || '', syncInterval: parseInt(this.syncIntervalInput?.value || '30000'), } }; // Update S3 config await window.electronAPI.invoke('config:update-s3', config.s3); // Update sync config await window.electronAPI.invoke('config:update-sync', config.sync); this.addActivityLog('success', 'Configuration saved successfully'); // Test S3 connection try { const testResult = await window.electronAPI.invoke('config:test-s3'); if (testResult.success) { this.addActivityLog('success', 'S3 connection test successful'); } else { this.addActivityLog('error', `S3 connection test failed: ${testResult.error}`); } } catch (error) { this.addActivityLog('error', `S3 connection test failed: ${error.message || 'Unknown error'}`); } } catch (error) { console.error('❌ Failed to save configuration:', error); this.addActivityLog('error', `Failed to save configuration: ${error.message || 'Unknown error'}`); } } /** * Force full sync */ async forceFullSync() { try { this.addActivityLog('info', '🔄 Starting force full sync...'); await window.electronAPI.invoke('sync:force-full'); this.addActivityLog('success', '✅ Force full sync completed'); } catch (error) { console.error('❌ Force full sync failed:', error); this.addActivityLog('error', `❌ Force full sync failed: ${error.message || 'Unknown error'}`); } } /** * Trigger immediate sync to propagate local changes */ async triggerImmediateSync() { try { this.addActivityLog('info', '🚀 Triggering immediate sync to propagate local changes...'); await window.electronAPI.invoke('sync:trigger-immediate'); this.addActivityLog('success', '✅ Immediate sync completed'); } catch (error) { console.error('❌ Immediate sync failed:', error); this.addActivityLog('error', `❌ Immediate sync failed: ${error.message || 'Unknown error'}`); } } /** * Export configuration to .env file */ async exportToEnv() { try { const result = await window.electronAPI.invoke('config:export-env'); if (result.success) { this.addActivityLog('success', 'Configuration exported to .env file successfully'); } else { this.addActivityLog('error', `Failed to export configuration: ${result.error}`); } } catch (error) { console.error('❌ Failed to export configuration:', error); this.addActivityLog('error', `Failed to export configuration: ${error.message || 'Unknown error'}`); } } /** * Add entry to activity log */ addActivityLog(type, message) { if (!this.activityLogElement) return; const timestamp = new Date().toLocaleTimeString(); const logEntry = document.createElement('div'); logEntry.className = `log-entry ${type}`; logEntry.innerHTML = ` [${timestamp}] ${message} `; this.activityLogElement.appendChild(logEntry); // Scroll to bottom this.activityLogElement.scrollTop = this.activityLogElement.scrollHeight; // Limit log entries to prevent memory issues const entries = this.activityLogElement.querySelectorAll('.log-entry'); if (entries.length > 100) { entries[0].remove(); } // Update the activity count in the header if it exists this.updateActivityCount(); } /** * Update activity count in the header */ updateActivityCount() { const entries = this.activityLogElement?.querySelectorAll('.log-entry') || []; const count = entries.length; // Find and update any activity count display const activityCountElement = document.querySelector('.activity-count'); if (activityCountElement) { activityCountElement.textContent = count; } } /** * Show notification */ showNotification(type, title, message) { // You could implement desktop notifications here } } // Initialize the renderer when DOM is loaded document.addEventListener('DOMContentLoaded', () => { try { window.rekordboxSyncRenderer = new RekordboxSyncRenderer(); } catch (error) { console.error('❌ Failed to initialize renderer:', error); } }); // Fallback initialization if DOMContentLoaded doesn't fire if (document.readyState === 'loading') { // Wait for DOMContentLoaded } else { try { window.rekordboxSyncRenderer = new RekordboxSyncRenderer(); } catch (error) { console.error('❌ Failed to initialize renderer (fallback):', error); } }