// DOM Elements
const dependencyOverlay = document.getElementById('dependency-overlay');
const depTitle = document.getElementById('dep-title');
const depMsg = document.getElementById('dep-msg');
const depProgress = document.getElementById('dep-progress');
const depPercent = document.getElementById('dep-percent');
const urlInput = document.getElementById('url-input');
const analyzeBtn = document.getElementById('analyze-btn');
const outputPath = document.getElementById('output-path');
const changeFolderBtn = document.getElementById('change-folder-btn');
const useCookiesCheckbox = document.getElementById('use-cookies');
const cookieBrowserSelect = document.getElementById('cookie-browser');
const errorCard = document.getElementById('error-card');
const errorMessage = document.getElementById('error-message');
const step1 = document.getElementById('step-1');
const step2 = document.getElementById('step-2');
const backBtn = document.getElementById('back-btn');
const trackList = document.getElementById('track-list');
const formatMp3Btn = document.getElementById('format-mp3-btn');
const formatMp4Btn = document.getElementById('format-mp4-btn');
const qualityPillsContainer = document.getElementById('quality-pills');
const downloadBtn = document.getElementById('download-btn');
const downloadProgressPanel = document.getElementById('download-progress-panel');
const downloadStatus = document.getElementById('download-status');
const downloadPercent = document.getElementById('download-percent');
const downloadProgressBar = document.getElementById('download-progress-bar');
const downloadSubtext = document.getElementById('download-subtext');
const overallPercent = document.getElementById('overall-percent');
const overallProgressBar = document.getElementById('overall-progress-bar');
const overallCountText = document.getElementById('overall-count-text');
const overallRemainingText = document.getElementById('overall-remaining-text');
const languageSelect = document.getElementById('language-select');
// State Variables
let analyzedUrl = '';
let currentFormat = 'mp3'; // 'mp3' or 'mp4'
let currentQuality = '320'; // Selected quality value
let currentVideoData = null;
// Playlist State Variables
const playlistHeader = document.getElementById('playlist-header');
const selectAllCheckbox = document.getElementById('select-all-checkbox');
const bulkMp3Btn = document.getElementById('bulk-mp3-btn');
const bulkMp4Btn = document.getElementById('bulk-mp4-btn');
let playlistTracks = [];
let focusedTrackId = null;
let isPlaylistMode = false;
// Localization State
let currentLang = 'tr';
let translations = {};
// 0. Translation Engine Functions
async function loadTranslations(lang) {
try {
const res = await fetch(`locales/${lang}.json`);
translations = await res.json();
currentLang = lang;
localStorage.setItem('vibe_lang', lang);
// Toggle direction attribute for Arabic (RTL)
if (lang === 'ar') {
document.documentElement.setAttribute('dir', 'rtl');
document.documentElement.setAttribute('lang', 'ar');
} else {
document.documentElement.removeAttribute('dir');
document.documentElement.setAttribute('lang', lang);
}
// Apply translations to data-i18n attributes
applyTranslations();
} catch (err) {
console.error('Error loading language file:', err);
}
}
function t(key, vars = {}) {
let text = translations[key] || key;
for (const [k, v] of Object.entries(vars)) {
text = text.replace(`%{${k}}`, v);
}
return text;
}
function applyTranslations() {
// Elements with data-i18n attribute
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.dataset.i18n;
el.textContent = t(key);
});
// Elements with data-i18n-placeholder attribute
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.dataset.i18nPlaceholder;
el.placeholder = t(key);
});
// Elements with data-i18n-title attribute
document.querySelectorAll('[data-i18n-title]').forEach(el => {
const key = el.dataset.i18nTitle;
el.title = t(key);
});
// Dynamically redraw active quality pills based on current locale
renderQualityPills();
// If track is rendered, translate its status and duration labels
if (currentVideoData) {
renderTrackList(currentVideoData);
}
}
// Quality Configuration Generator (Dynamic for Localization)
function getQualityConfigs() {
return {
mp3: [
{ label: `320 kbps (${t('quality_best')})`, value: '320' },
{ label: '256 kbps', value: '256' },
{ label: '192 kbps', value: '192' }
],
mp4: [
{ label: '1080p (Full HD)', value: '1080' },
{ label: '720p (HD)', value: '720' },
{ label: `480p (${t('quality_standard')})`, value: '480' }
]
};
}
// 1. Initial Load & Dependency Verification
window.addEventListener('DOMContentLoaded', async () => {
// Setup Custom Titlebar Controls
document.getElementById('titlebar-minimize').addEventListener('click', () => {
window.api.minimizeWindow();
});
document.getElementById('titlebar-close').addEventListener('click', () => {
window.api.closeWindow();
});
// Setup Language Selector Change Handler
languageSelect.addEventListener('change', async (e) => {
await loadTranslations(e.target.value);
});
// Select All Checkbox Handler
selectAllCheckbox.addEventListener('change', () => {
const checked = selectAllCheckbox.checked;
playlistTracks.forEach(track => {
track.checked = checked;
});
renderTrackList(currentVideoData);
});
// Bulk MP3 Button Handler
bulkMp3Btn.addEventListener('click', () => {
playlistTracks.forEach(track => {
if (track.checked) {
track.format = 'mp3';
track.quality = '320';
}
});
const focusedTrack = playlistTracks.find(t => t.id === focusedTrackId);
if (focusedTrack) syncBottomPanelWithTrack(focusedTrack);
renderTrackList(currentVideoData);
});
// Bulk MP4 Button Handler
bulkMp4Btn.addEventListener('click', () => {
playlistTracks.forEach(track => {
if (track.checked) {
track.format = 'mp4';
track.quality = '1080';
}
});
const focusedTrack = playlistTracks.find(t => t.id === focusedTrackId);
if (focusedTrack) syncBottomPanelWithTrack(focusedTrack);
renderTrackList(currentVideoData);
});
const speedSelect = document.getElementById('summary-speed-select');
if (speedSelect) {
speedSelect.addEventListener('change', () => {
updateSummaryStats();
});
}
// Get and load persisted or default language
const savedLang = localStorage.getItem('vibe_lang') || 'tr';
languageSelect.value = savedLang;
await loadTranslations(savedLang);
// Get and display default downloads folder path
const defaultPath = await window.api.getDownloadsFolder();
outputPath.textContent = defaultPath;
// Listen to dependency checking progress
window.api.onDependencyStatus(({ message, progress }) => {
let msg = message;
if (message.includes('Downloading yt-dlp')) {
const pctMatch = message.match(/(\d+)%/);
msg = pctMatch ? `${t('btn_downloading')} yt-dlp... ${pctMatch[1]}%` : `${t('btn_downloading')} yt-dlp...`;
} else if (message.includes('Downloading ffmpeg')) {
const pctMatch = message.match(/(\d+)%/);
msg = pctMatch ? `${t('btn_downloading')} ffmpeg... ${pctMatch[1]}%` : `${t('btn_downloading')} ffmpeg...`;
} else if (message.includes('Extracting ffmpeg')) {
msg = `${t('status_processing')} ffmpeg...`;
} else if (message.includes('Dependencies ready')) {
msg = t('status_completed');
}
depMsg.textContent = msg;
depProgress.style.width = `${progress}%`;
depPercent.textContent = `${progress}%`;
});
try {
const result = await window.api.checkDependencies();
if (result.success) {
// Transition out the loading overlay
dependencyOverlay.style.opacity = '0';
setTimeout(() => {
dependencyOverlay.classList.remove('active');
}, 400);
} else {
depTitle.textContent = t('badge_error');
depMsg.textContent = `${t('error_default')} ${result.error}`;
depPercent.textContent = t('badge_error');
}
} catch (err) {
depTitle.textContent = t('badge_error');
depMsg.textContent = err.message;
}
});
// 2. Output Path Folder Changer
changeFolderBtn.addEventListener('click', async () => {
const newPath = await window.api.selectDirectory();
outputPath.textContent = newPath;
});
// 3. Format Toggle Event Listeners
formatMp3Btn.addEventListener('click', () => {
if (currentFormat === 'mp3') return;
currentFormat = 'mp3';
formatMp3Btn.classList.add('active');
formatMp4Btn.classList.remove('active');
currentQuality = '320'; // Reset to default best quality for MP3
renderQualityPills();
applyBottomSettingsToSelected();
});
formatMp4Btn.addEventListener('click', () => {
if (currentFormat === 'mp4') return;
currentFormat = 'mp4';
formatMp4Btn.classList.add('active');
formatMp3Btn.classList.remove('active');
currentQuality = '1080'; // Reset to default best quality for MP4
renderQualityPills();
applyBottomSettingsToSelected();
});
// 4. Quality Pills Renderer
function renderQualityPills() {
qualityPillsContainer.innerHTML = '';
const configs = getQualityConfigs();
const options = configs[currentFormat];
options.forEach(opt => {
const pill = document.createElement('button');
pill.className = 'quality-pill';
if (opt.value === currentQuality) {
pill.classList.add('active');
}
pill.textContent = opt.label;
pill.dataset.value = opt.value;
pill.addEventListener('click', () => {
document.querySelectorAll('.quality-pill').forEach(p => p.classList.remove('active'));
pill.classList.add('active');
currentQuality = opt.value;
applyBottomSettingsToSelected();
});
qualityPillsContainer.appendChild(pill);
});
}
function syncBottomPanelWithTrack(track) {
currentFormat = track.format;
currentQuality = track.quality;
if (currentFormat === 'mp3') {
formatMp3Btn.classList.add('active');
formatMp4Btn.classList.remove('active');
} else {
formatMp4Btn.classList.add('active');
formatMp3Btn.classList.remove('active');
}
renderQualityPills();
}
function applyBottomSettingsToSelected() {
if (!currentVideoData) return;
const checkedTracks = playlistTracks.filter(t => t.checked);
if (checkedTracks.length > 0) {
checkedTracks.forEach(track => {
track.format = currentFormat;
track.quality = currentQuality;
});
} else if (focusedTrackId) {
const focusedTrack = playlistTracks.find(t => t.id === focusedTrackId);
if (focusedTrack) {
focusedTrack.format = currentFormat;
focusedTrack.quality = currentQuality;
}
}
renderTrackList(currentVideoData);
}
function updateSelectAllCheckboxState() {
const checkedCount = playlistTracks.filter(t => t.checked).length;
selectAllCheckbox.checked = checkedCount === playlistTracks.length;
}
// Stats Calculator Helpers
function parseDurationToSeconds(durationStr) {
if (!durationStr || durationStr === t('duration_unknown') || durationStr === 'Unknown') return 0;
const parts = durationStr.split(':').map(Number);
if (parts.some(isNaN)) return 0;
if (parts.length === 3) {
// h:mm:ss
return parts[0] * 3600 + parts[1] * 60 + parts[2];
} else if (parts.length === 2) {
// m:ss
return parts[0] * 60 + parts[1];
} else if (parts.length === 1) {
return parts[0];
}
return 0;
}
function formatSecondsToReadable(totalSeconds) {
if (!totalSeconds) return '0 ' + (t('sec_suffix_short') || 'sn');
const hrs = Math.floor(totalSeconds / 3600);
const mins = Math.floor((totalSeconds % 3600) / 60);
const secs = Math.floor(totalSeconds % 60);
let result = [];
if (hrs > 0) {
result.push(`${hrs} ${t('hr_suffix_short') || 'sa'}`);
}
if (mins > 0 || hrs > 0) {
result.push(`${mins} ${t('min_suffix_short') || 'dk'}`);
}
if (secs > 0 || (hrs === 0 && mins === 0)) {
result.push(`${secs} ${t('sec_suffix_short') || 'sn'}`);
}
return result.join(' ');
}
function updateSummaryStats() {
const summaryCard = document.getElementById('playlist-summary-card');
if (!summaryCard) return;
const checkedTracks = playlistTracks.filter(t => t.checked);
if (checkedTracks.length === 0) {
document.getElementById('summary-total-count').textContent = '0';
document.getElementById('summary-total-duration').textContent = formatSecondsToReadable(0);
document.getElementById('summary-est-size').textContent = '0 MB';
document.getElementById('summary-est-time').textContent = '~0 ' + (t('sec_suffix_short') || 'sn');
// Hide duration items when empty
document.getElementById('summary-item-duration').style.display = 'none';
document.getElementById('summary-item-speed').style.display = 'none';
document.getElementById('summary-item-size').style.display = 'none';
document.getElementById('summary-item-time').style.display = 'none';
return;
}
// Calculate total count
document.getElementById('summary-total-count').textContent = checkedTracks.length;
// Check if any of the checked tracks has unknown duration
const hasUnknownDuration = checkedTracks.some(track => {
const secs = parseDurationToSeconds(track.duration);
return secs <= 0 || !track.duration || track.duration.includes('Bilinmiyor') || track.duration === 'Unknown';
});
if (hasUnknownDuration) {
// Hide duration-related items completely
document.getElementById('summary-item-duration').style.display = 'none';
document.getElementById('summary-item-speed').style.display = 'none';
document.getElementById('summary-item-size').style.display = 'none';
document.getElementById('summary-item-time').style.display = 'none';
} else {
// Show duration-related items
document.getElementById('summary-item-duration').style.display = 'flex';
document.getElementById('summary-item-speed').style.display = 'flex';
document.getElementById('summary-item-size').style.display = 'flex';
document.getElementById('summary-item-time').style.display = 'flex';
// Calculate total duration
let totalSeconds = 0;
checkedTracks.forEach(track => {
totalSeconds += parseDurationToSeconds(track.duration);
});
document.getElementById('summary-total-duration').textContent = formatSecondsToReadable(totalSeconds);
// Calculate estimated size in MB
let totalSizeMB = 0;
checkedTracks.forEach(track => {
const duration = parseDurationToSeconds(track.duration);
if (track.format === 'mp3') {
const kbps = parseInt(track.quality) || 320;
totalSizeMB += (duration * kbps) / 8388.608;
} else {
let bitrate = 1500;
if (track.quality === '1080') bitrate = 3000;
else if (track.quality === '480') bitrate = 750;
totalSizeMB += (duration * bitrate) / 8388.608;
}
});
document.getElementById('summary-est-size').textContent = `${totalSizeMB.toFixed(1)} MB`;
// Calculate estimated download time based on selected internet speed
const speedSelect = document.getElementById('summary-speed-select');
const speedMbps = parseFloat(speedSelect.value) || 24;
const sizeMegabits = totalSizeMB * 8;
let estSeconds = sizeMegabits / speedMbps;
// Realism factor: connection handshake / conversion overhead
estSeconds += checkedTracks.length * 1.5;
document.getElementById('summary-est-time').textContent = `~${formatSecondsToReadable(Math.round(estSeconds))}`;
}
}
// 5. Back Button (Step 2 to Step 1 Transition)
backBtn.addEventListener('click', () => {
// Reset any error card
errorCard.style.display = 'none';
// Fade out Step 2 and show Step 1
step2.classList.remove('active');
step1.classList.add('active');
const summaryCard = document.getElementById('playlist-summary-card');
if (summaryCard) {
summaryCard.style.display = 'none';
}
currentVideoData = null;
playlistTracks = [];
focusedTrackId = null;
urlInput.focus();
});
// 6. Link Analysis Handler
analyzeBtn.addEventListener('click', async () => {
const url = urlInput.value.trim();
if (!url) return;
// Reset error displays
errorCard.style.display = 'none';
// Toggle button loading states
analyzeBtn.disabled = true;
const analyzeSpan = analyzeBtn.querySelector('span');
const analyzeIcon = analyzeBtn.querySelector('.btn-icon');
analyzeSpan.textContent = t('btn_analyzing');
analyzeIcon.style.display = 'inline-block';
try {
const browser = useCookiesCheckbox.checked ? cookieBrowserSelect.value : null;
const result = await window.api.getVideoInfo(url, browser);
if (result.success) {
analyzedUrl = url;
currentVideoData = result.info;
playlistTracks = [];
focusedTrackId = null;
// Render the fetched video details inside the scrollable list container
renderTrackList(result.info);
// Wizard transition: Step 1 -> Step 2
step1.classList.remove('active');
step2.classList.add('active');
} else {
showError(result.error);
}
} catch (err) {
showError(err.message);
} finally {
analyzeBtn.disabled = false;
analyzeSpan.textContent = t('btn_analyze');
analyzeIcon.style.display = 'none';
}
});
// 7. Render Track List Function
function renderTrackList(info) {
trackList.innerHTML = '';
if (info.isPlaylist) {
isPlaylistMode = true;
playlistHeader.style.display = 'flex';
} else {
isPlaylistMode = false;
playlistHeader.style.display = 'none';
}
const summaryCard = document.getElementById('playlist-summary-card');
if (summaryCard) {
summaryCard.style.display = 'flex';
}
if (info.entries) {
info.entries.forEach(entry => {
const existing = playlistTracks.find(t => t.id === entry.id);
if (!existing) {
playlistTracks.push({
id: entry.id,
title: entry.title,
thumbnail: entry.thumbnail,
duration: entry.duration,
uploader: entry.uploader,
url: entry.url,
format: currentFormat,
quality: currentQuality,
status: 'ready',
progress: 0,
checked: true
});
}
});
}
if (playlistTracks.length > 0 && !focusedTrackId) {
focusedTrackId = playlistTracks[0].id;
}
const focusedTrack = playlistTracks.find(t => t.id === focusedTrackId);
if (focusedTrack) {
syncBottomPanelWithTrack(focusedTrack);
}
playlistTracks.forEach(track => {
const itemEl = document.createElement('div');
itemEl.className = `track-item ${track.id === focusedTrackId ? 'focused' : ''}`;
itemEl.dataset.id = track.id;
const checkboxWrapper = document.createElement('div');
checkboxWrapper.className = 'track-checkbox-wrapper';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'track-checkbox';
checkbox.checked = track.checked;
checkbox.addEventListener('change', (e) => {
e.stopPropagation();
track.checked = checkbox.checked;
updateSelectAllCheckboxState();
});
checkboxWrapper.appendChild(checkbox);
itemEl.appendChild(checkboxWrapper);
const thumbEl = document.createElement('div');
thumbEl.className = 'track-thumb';
thumbEl.innerHTML = `
`;
itemEl.appendChild(thumbEl);
const detailsEl = document.createElement('div');
detailsEl.className = 'track-details';
detailsEl.innerHTML = `
${track.uploader}
${t('duration_label')} ${track.duration || t('duration_unknown')} `; itemEl.appendChild(detailsEl); const formatSelectorEl = document.createElement('div'); formatSelectorEl.className = 'track-format-selector'; const formatBadgeBtn = document.createElement('button'); formatBadgeBtn.className = `format-badge-btn ${track.format}`; formatBadgeBtn.textContent = track.format.toUpperCase(); formatBadgeBtn.addEventListener('click', (e) => { e.stopPropagation(); track.format = track.format === 'mp3' ? 'mp4' : 'mp3'; track.quality = track.format === 'mp3' ? '320' : '1080'; if (track.id === focusedTrackId) { syncBottomPanelWithTrack(track); } renderTrackList(currentVideoData); }); formatSelectorEl.appendChild(formatBadgeBtn); itemEl.appendChild(formatSelectorEl); const statusEl = document.createElement('div'); statusEl.className = 'track-status'; let statusText = t('badge_ready'); let badgeClass = 'ready'; if (track.status === 'downloading') { statusText = track.progress > 0 ? `${t('badge_downloading')} %${track.progress}` : t('badge_downloading'); badgeClass = 'downloading'; } else if (track.status === 'completed') { statusText = t('badge_completed'); badgeClass = 'completed'; } else if (track.status === 'error') { statusText = t('badge_error'); badgeClass = 'error'; } else if (track.status === 'waiting') { statusText = t('status_connecting'); badgeClass = 'ready'; } const statusBadge = document.createElement('span'); statusBadge.id = `badge-${track.id}`; statusBadge.className = `status-badge ${badgeClass}`; if (track.status === 'completed') { statusBadge.style.borderColor = 'rgba(0, 255, 135, 0.3)'; statusBadge.style.color = '#00ff87'; statusBadge.style.background = 'rgba(0, 255, 135, 0.1)'; } else if (track.status === 'error') { statusBadge.style.borderColor = 'rgba(255, 77, 77, 0.3)'; statusBadge.style.color = '#ff4d4d'; statusBadge.style.background = 'rgba(255, 77, 77, 0.1)'; } statusBadge.textContent = statusText; statusEl.appendChild(statusBadge); itemEl.appendChild(statusEl); itemEl.addEventListener('click', () => { focusedTrackId = track.id; syncBottomPanelWithTrack(track); renderTrackList(currentVideoData); }); trackList.appendChild(itemEl); }); updateSummaryStats(); } // 8. Media Download Executor downloadBtn.addEventListener('click', async () => { if (!analyzedUrl || playlistTracks.length === 0) return; const selectedItems = playlistTracks.filter(t => t.checked); if (selectedItems.length === 0) { Swal.fire({ title: t('badge_error'), text: t('err_no_items_selected') || 'Lütfen indirmek için en az bir dosya seçin.', icon: 'warning', background: 'rgba(23, 20, 38, 0.95)', color: '#fff', confirmButtonColor: '#7000ff' }); return; } // Setup download progress panel UI states downloadProgressPanel.style.display = 'block'; downloadBtn.disabled = true; backBtn.disabled = true; downloadStatus.textContent = t('status_connecting'); downloadPercent.textContent = '0%'; downloadProgressBar.style.width = '0%'; downloadSubtext.textContent = t('subtext_wait'); // Initialize overall progress bar UI values overallPercent.textContent = '0%'; overallProgressBar.style.width = '0%'; if (currentLang === 'tr') { overallCountText.textContent = `Tamamlanan: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `Kalan: ${selectedItems.length}`; } else if (currentLang === 'de') { overallCountText.textContent = `Abgeschlossen: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `Verbleibend: ${selectedItems.length}`; } else if (currentLang === 'ru') { overallCountText.textContent = `Завершено: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `Осталось: ${selectedItems.length}`; } else if (currentLang === 'ar') { overallCountText.textContent = `اكتمل: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `المتبقي: ${selectedItems.length}`; } else { overallCountText.textContent = `Completed: 0 / ${selectedItems.length}`; overallRemainingText.textContent = `Remaining: ${selectedItems.length}`; } // Disable UI interactive components during download document.querySelectorAll('.track-checkbox, .format-badge-btn, .quality-pill, .format-btn').forEach(el => { el.disabled = true; el.style.pointerEvents = 'none'; }); // Mark selected items as waiting selectedItems.forEach(track => { track.status = 'waiting'; track.progress = 0; const badge = document.getElementById(`badge-${track.id}`); if (badge) { badge.textContent = t('status_connecting'); badge.className = 'status-badge ready'; } }); // Subscribe to progress events from the main process window.api.onDownloadProgress(({ itemId, index, total, percent, status, title, failed }) => { const track = playlistTracks.find(t => t.id === itemId); if (track) { if (percent === 100) { track.status = 'completed'; track.progress = 100; } else if (failed) { track.status = 'error'; } else { track.status = 'downloading'; track.progress = percent; } } // Dynamic row updates const badge = document.getElementById(`badge-${itemId}`); if (badge) { if (percent === 100) { badge.textContent = t('badge_completed'); badge.className = 'status-badge completed'; badge.style.borderColor = 'rgba(0, 255, 135, 0.3)'; badge.style.color = '#00ff87'; badge.style.background = 'rgba(0, 255, 135, 0.1)'; } else if (failed) { badge.textContent = t('badge_error'); badge.className = 'status-badge error'; badge.style.borderColor = 'rgba(255, 77, 77, 0.3)'; badge.style.color = '#ff4d4d'; badge.style.background = 'rgba(255, 77, 77, 0.1)'; } else { badge.textContent = `${t('badge_downloading')} %${percent}`; badge.className = 'status-badge downloading'; } } // Update main footer progress panel let stat = status; if (status.includes('İndiriliyor')) { stat = `${t('btn_downloading')}... %${percent}`; } else if (status.includes('İşleniyor') || status.includes('Dönüştürülüyor')) { stat = t('status_processing'); } downloadStatus.textContent = `[${index + 1}/${total}] ${title}`; downloadPercent.textContent = `${percent}%`; downloadProgressBar.style.width = `${percent}%`; downloadSubtext.textContent = `${t('swal_file_size')}: ${stat}`; // Overall progress, completed vs remaining items live calculator const totalSelected = playlistTracks.filter(t => t.checked).length; const completedCount = playlistTracks.filter(t => t.checked && t.status === 'completed').length; const failedCount = playlistTracks.filter(t => t.checked && t.status === 'error').length; const remainingCount = totalSelected - completedCount - failedCount; // Calculate smooth continuous overall progress percentage const overallVal = ((completedCount + (percent / 100)) / totalSelected) * 100; const overallRound = Math.min(100, Math.round(overallVal)); overallPercent.textContent = `${overallRound}%`; overallProgressBar.style.width = `${overallRound}%`; if (currentLang === 'tr') { overallCountText.textContent = `Tamamlanan: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `Kalan: ${remainingCount}${failedCount > 0 ? ` · Hata: ${failedCount}` : ''}`; } else if (currentLang === 'de') { overallCountText.textContent = `Abgeschlossen: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `Verbleibend: ${remainingCount}${failedCount > 0 ? ` · Fehler: ${failedCount}` : ''}`; } else if (currentLang === 'ru') { overallCountText.textContent = `Завершено: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `Осталось: ${remainingCount}${failedCount > 0 ? ` · Ошибки: ${failedCount}` : ''}`; } else if (currentLang === 'ar') { overallCountText.textContent = `اكتمل: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `المتبقي: ${remainingCount}${failedCount > 0 ? ` · خطأ: ${failedCount}` : ''}`; } else { overallCountText.textContent = `Completed: ${completedCount} / ${totalSelected}`; overallRemainingText.textContent = `Remaining: ${remainingCount}${failedCount > 0 ? ` · Error: ${failedCount}` : ''}`; } }); try { const browser = useCookiesCheckbox.checked ? cookieBrowserSelect.value : null; // Map selected items into a simplified API task schema const downloadTasks = selectedItems.map(t => ({ id: t.id, title: t.title, url: t.url, format: t.format, quality: t.quality })); const result = await window.api.downloadMedia(downloadTasks, browser); if (result.success) { downloadStatus.textContent = t('status_completed'); downloadPercent.textContent = '100%'; downloadProgressBar.style.width = '100%'; downloadSubtext.textContent = t('subtext_success'); // Success gradient transitions downloadProgressBar.style.background = 'linear-gradient(90deg, #00ff87 0%, #60efff 100%)'; downloadProgressBar.style.boxShadow = '0 0 15px rgba(0, 255, 135, 0.6)'; const displayTitle = isPlaylistMode ? `${currentVideoData.playlistTitle || 'Playlist'} (${result.stats.completed}/${result.stats.totalItems})` : selectedItems[0].title; const displayUploader = isPlaylistMode ? 'Playlist Queue' : (selectedItems[0].uploader || t('duration_unknown')); const displaySize = isPlaylistMode ? `${result.stats.completed} ${t('badge_completed').toLowerCase()}` : (result.stats.results[0]?.size || 'Bilinmiyor'); const localizedDuration = result.stats.duration .replace('saniye', t('sec_suffix')) .replace('dakika', t('min_suffix')); // Trigger premium SweetAlert2 modal Swal.fire({ title: t('swal_success_title'), html: `${isPlaylistMode ? t('playlist_title_label') || 'Çalma Listesi:' : t('swal_file_name')} ${displayTitle}
${t('swal_platform_uploader')} ${displayUploader}
${isPlaylistMode ? t('downloaded_files_label') || 'İndirilen Dosyalar:' : t('swal_file_size')} ${displaySize}
${t('swal_duration')} ${localizedDuration}
${t('swal_output_dir')}
${result.stats.outputDir}