class RekordboxSyncRenderer { constructor() { this.initializeElements(); this.setupEventListeners(); this.setupElectronListeners(); this.loadInitialState(); } /** * Initialize DOM elements */ initializeElements() { // Buttons this.startBtn = document.getElementById('startSyncBtn'); this.stopBtn = document.getElementById('stopSyncBtn'); 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'); // 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() { console.log('đ Setting up Electron IPC listeners...'); console.log('đ Window object:', window); console.log('đ Electron API object:', window.electronAPI); if (!window.electronAPI) { console.error('â Electron API not available'); console.error('â This means the preload script failed to load'); return; } console.log('â Electron API is available, setting up listeners...'); // Sync status updates window.electronAPI.on('sync-status-changed', (status) => { console.log('đ Received sync status update:', status); console.log('đ Status details:', { isRunning: status.isRunning, currentPhase: status.currentPhase, actualFileCount: status.actualFileCount, stats: status.stats }); console.log('đ Calling updateSyncStatus...'); this.updateSyncStatus(status); console.log('â updateSyncStatus completed'); }); // 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; // Reset progress bar on error const progressBar = document.getElementById('progressBar'); if (progressBar) { progressBar.style.width = '0%'; } }); // 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'}`); } } /** * Start sync */ async startSync() { try { this.addActivityLog('info', 'Starting sync...'); await window.electronAPI.invoke('sync:start'); this.addActivityLog('success', 'Sync started successfully'); } catch (error) { console.error('â Failed to start sync:', error); this.addActivityLog('error', `Failed to start sync: ${error.message || 'Unknown error'}`); } } /** * Stop sync */ async stopSync() { try { this.addActivityLog('info', 'Stopping sync...'); await window.electronAPI.invoke('sync:stop'); this.addActivityLog('info', 'Sync stopped'); } catch (error) { console.error('â Failed to stop sync:', error); this.addActivityLog('error', `Failed to stop sync: ${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'; if (this.startBtn) this.startBtn.disabled = true; if (this.stopBtn) this.stopBtn.disabled = false; } 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'; // Reset progress bar when stopped const progressBar = document.getElementById('progressBar'); if (progressBar) { progressBar.style.width = '0%'; } } 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}`); } // Update progress bar if (status.progress && status.progress.percent > 0) { const progressBar = document.getElementById('progressBar'); if (progressBar) { progressBar.style.width = `${status.progress.percent}%`; } // Only log progress changes to reduce spam 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 += `