585 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 += `<div class="status-detail pending">📋 ${status.pendingCount} pending</div>`;
}
if (status.inProgressCount > 0) {
detailsHTML += `<div class="status-detail in-progress">🔄 ${status.inProgressCount} in progress</div>`;
}
if (status.completedCount > 0) {
detailsHTML += `<div class="status-detail completed">✅ ${status.completedCount} completed</div>`;
}
if (status.failedCount > 0) {
detailsHTML += `<div class="status-detail failed">❌ ${status.failedCount} failed</div>`;
}
if (status.errors && status.errors.length > 0) {
detailsHTML += `<div class="status-detail errors">⚠️ ${status.errors.length} errors</div>`;
}
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 = `
<span class="log-timestamp">[${timestamp}]</span>
<span class="log-message">${message}</span>
`;
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);
}
}