loreal-video-optimizer/frontend/utils.js
2026-01-08 18:18:48 +05:30

411 lines
14 KiB
JavaScript

/**
* Utility Functions for Quality of Life Improvements
*/
// ==============================================================================
// LOCAL STORAGE MANAGEMENT
// ==============================================================================
const Storage = {
/**
* Save last used settings
*/
saveLastSettings(platform, aspectRatio) {
try {
localStorage.setItem('lastPlatform', platform);
localStorage.setItem('lastAspectRatio', aspectRatio);
} catch (e) {
console.warn('Failed to save settings to localStorage:', e);
}
},
/**
* Load last used settings
*/
getLastSettings() {
try {
return {
platform: localStorage.getItem('lastPlatform'),
aspectRatio: localStorage.getItem('lastAspectRatio')
};
} catch (e) {
console.warn('Failed to load settings from localStorage:', e);
return { platform: null, aspectRatio: null };
}
},
/**
* Clear saved settings
*/
clearSettings() {
try {
localStorage.removeItem('lastPlatform');
localStorage.removeItem('lastAspectRatio');
} catch (e) {
console.warn('Failed to clear settings from localStorage:', e);
}
}
};
// ==============================================================================
// CLIPBOARD UTILITIES
// ==============================================================================
const Clipboard = {
/**
* Copy text to clipboard with fallback
*/
async copy(text) {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
return true;
} else {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.select();
const success = document.execCommand('copy');
document.body.removeChild(textArea);
return success;
}
} catch (error) {
console.error('Failed to copy to clipboard:', error);
return false;
}
},
/**
* Copy video specs to clipboard
*/
async copyVideoSpecs(videoInfo, platform, aspectRatio) {
const specs = `
Video Specifications:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Platform: ${platform || 'N/A'}
Aspect Ratio: ${aspectRatio || 'N/A'}
Duration: ${videoInfo.duration ? Math.round(videoInfo.duration) + 's' : 'N/A'}
Resolution: ${videoInfo.width || '?'}x${videoInfo.height || '?'}
Bitrate: ${videoInfo.bitrate ? Math.round(videoInfo.bitrate / 1000) + ' kbps' : 'N/A'}
Codec: ${videoInfo.codec || 'N/A'}
`.trim();
const success = await this.copy(specs);
if (success) {
toast.success('Video specs copied to clipboard!');
} else {
toast.error('Failed to copy to clipboard');
}
},
/**
* Copy conversion results to clipboard
*/
async copyConversionResults(results) {
const text = `
Conversion Results:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Input Size: ${formatFileSize(results.input_size)}
Output Size: ${formatFileSize(results.output_size)}
Size Reduction: ${results.size_reduction_percent}%
Space Saved: ${formatFileSize(results.input_size - results.output_size)}
`.trim();
const success = await this.copy(text);
if (success) {
toast.success('Conversion results copied to clipboard!');
} else {
toast.error('Failed to copy to clipboard');
}
}
};
// ==============================================================================
// KEYBOARD SHORTCUTS
// ==============================================================================
const KeyboardShortcuts = {
init() {
document.addEventListener('keydown', (e) => {
// ESC - Clear/Reset
if (e.key === 'Escape') {
this.handleEscape();
}
// Ctrl/Cmd + Enter - Convert (if ready)
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
this.handleConvert();
}
// Ctrl/Cmd + K - Open help
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
window.location.href = 'help.html';
}
});
},
handleEscape() {
// Close modals, clear selections, etc.
const configSection = document.getElementById('configSection');
const comparisonSection = document.getElementById('comparisonSection');
const uploadSection = document.getElementById('uploadSection');
if (comparisonSection && comparisonSection.style.display !== 'none') {
// Go back to upload
if (confirm('Start over with a new video?')) {
comparisonSection.style.display = 'none';
if (configSection) configSection.style.display = 'none';
if (uploadSection) uploadSection.style.display = 'block';
window.currentFileId = null;
toast.info('Ready for new upload');
}
} else if (configSection && configSection.style.display !== 'none') {
// Go back to upload
if (confirm('Cancel and start over?')) {
configSection.style.display = 'none';
if (uploadSection) uploadSection.style.display = 'block';
window.currentFileId = null;
toast.info('Upload cancelled');
}
}
},
handleConvert() {
const convertBtn = document.getElementById('convertBtn');
if (convertBtn && !convertBtn.disabled) {
convertBtn.click();
toast.info('Starting conversion... (Ctrl+Enter)');
}
}
};
// ==============================================================================
// FILE SIZE FORMATTING
// ==============================================================================
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
// ==============================================================================
// TIME ESTIMATION
// ==============================================================================
function estimateConversionTime(fileSize, platform) {
// Rough estimates based on file size (in MB)
const sizeInMB = fileSize / (1024 * 1024);
// Base processing time: ~2-4 seconds per MB for H264
const baseTime = sizeInMB * 3;
// Add overhead for different platforms
const overhead = 5; // seconds
const totalSeconds = Math.max(10, baseTime + overhead);
if (totalSeconds < 60) {
return `~${Math.round(totalSeconds)} seconds`;
} else {
const minutes = Math.floor(totalSeconds / 60);
const seconds = Math.round(totalSeconds % 60);
return `~${minutes}m ${seconds}s`;
}
}
// ==============================================================================
// DRAG & DROP IMPROVEMENTS
// ==============================================================================
const DragDropEnhancer = {
init(dropZoneElement) {
if (!dropZoneElement) return;
let dragCounter = 0;
dropZoneElement.addEventListener('dragenter', (e) => {
e.preventDefault();
dragCounter++;
if (dragCounter === 1) {
dropZoneElement.classList.add('drag-over');
dropZoneElement.style.borderColor = '#FFC407';
dropZoneElement.style.background = 'rgba(255, 196, 7, 0.1)';
}
});
dropZoneElement.addEventListener('dragleave', (e) => {
e.preventDefault();
dragCounter--;
if (dragCounter === 0) {
dropZoneElement.classList.remove('drag-over');
dropZoneElement.style.borderColor = '';
dropZoneElement.style.background = '';
}
});
dropZoneElement.addEventListener('dragover', (e) => {
e.preventDefault();
});
dropZoneElement.addEventListener('drop', (e) => {
e.preventDefault();
dragCounter = 0;
dropZoneElement.classList.remove('drag-over');
dropZoneElement.style.borderColor = '';
dropZoneElement.style.background = '';
});
}
};
// ==============================================================================
// BETTER ERROR MESSAGES
// ==============================================================================
const ErrorHandler = {
getHelpfulErrorMessage(error) {
const errorStr = error.toString().toLowerCase();
if (errorStr.includes('network') || errorStr.includes('fetch')) {
return {
message: 'Network Error',
details: 'Cannot connect to the server. Please check your internet connection.',
action: 'Try refreshing the page or contact your administrator.'
};
}
if (errorStr.includes('timeout')) {
return {
message: 'Request Timeout',
details: 'The operation took too long to complete.',
action: 'Try with a smaller video file or try again later.'
};
}
if (errorStr.includes('file size') || errorStr.includes('too large')) {
return {
message: 'File Too Large',
details: 'The video file exceeds the maximum allowed size (500MB).',
action: 'Compress your video before uploading or use a smaller file.'
};
}
if (errorStr.includes('unauthorized') || errorStr.includes('403')) {
return {
message: 'Access Denied',
details: 'You do not have permission to perform this action.',
action: 'Please sign in again or contact your administrator.'
};
}
if (errorStr.includes('ffmpeg') || errorStr.includes('codec')) {
return {
message: 'Conversion Error',
details: 'The video conversion failed due to encoding issues.',
action: 'Try a different video format or contact support.'
};
}
return {
message: 'Error',
details: error.toString(),
action: 'Please try again or contact support if the problem persists.'
};
},
show(error) {
const helpful = this.getHelpfulErrorMessage(error);
toast.error(`${helpful.message}: ${helpful.details}\n${helpful.action}`, 8000);
}
};
// ==============================================================================
// LOADING STATE IMPROVEMENTS
// ==============================================================================
const LoadingState = {
show(message = 'Processing...') {
let loader = document.getElementById('global-loader');
if (!loader) {
loader = document.createElement('div');
loader.id = 'global-loader';
loader.innerHTML = `
<div style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
">
<div style="
width: 60px;
height: 60px;
border: 4px solid #333;
border-top-color: #FFC407;
border-radius: 50%;
animation: spin 1s linear infinite;
"></div>
<p id="loader-message" style="
color: #FFC407;
font-family: 'Montserrat', sans-serif;
font-size: 18px;
margin-top: 20px;
">${message}</p>
</div>
`;
document.body.appendChild(loader);
// Add spin animation
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
} else {
loader.style.display = 'flex';
const messageEl = document.getElementById('loader-message');
if (messageEl) messageEl.textContent = message;
}
},
hide() {
const loader = document.getElementById('global-loader');
if (loader) {
loader.style.display = 'none';
}
},
updateMessage(message) {
const messageEl = document.getElementById('loader-message');
if (messageEl) messageEl.textContent = message;
}
};
// ==============================================================================
// EXPORT UTILITIES
// ==============================================================================
// Make utilities globally available
window.Storage = Storage;
window.Clipboard = Clipboard;
window.KeyboardShortcuts = KeyboardShortcuts;
window.formatFileSize = formatFileSize;
window.estimateConversionTime = estimateConversionTime;
window.DragDropEnhancer = DragDropEnhancer;
window.ErrorHandler = ErrorHandler;
window.LoadingState = LoadingState;