feat: implement UI layout and internationalization support for YouTube downloader

This commit is contained in:
Ümit Tunç
2026-05-28 07:45:58 +03:00
parent a4ec059ae2
commit 8f2a64cd33
8 changed files with 281 additions and 5 deletions
+30
View File
@@ -157,6 +157,36 @@
</div>
</div>
<!-- Playlist Summary Statistics Panel -->
<div id="playlist-summary-card" class="playlist-summary-card" style="display: none;">
<div class="summary-stat-item">
<span class="summary-stat-label" data-i18n="summary_total_tracks">Toplam Dosya:</span>
<span id="summary-total-count" class="summary-stat-value">0</span>
</div>
<div class="summary-stat-item">
<span class="summary-stat-label" data-i18n="summary_total_duration">Toplam Süre:</span>
<span id="summary-total-duration" class="summary-stat-value">0 sn</span>
</div>
<div class="summary-stat-item summary-stat-speed-item">
<span class="summary-stat-label" data-i18n="summary_speed_label">Hız:</span>
<select id="summary-speed-select" class="summary-speed-select">
<option value="100">100 Mbps (Fiber)</option>
<option value="50">50 Mbps (VDSL)</option>
<option value="24" selected>24 Mbps (ADSL/VDSL)</option>
<option value="16">16 Mbps (ADSL)</option>
<option value="8">8 Mbps (Temel)</option>
</select>
</div>
<div class="summary-stat-item">
<span class="summary-stat-label" data-i18n="summary_est_size">Tahmini Boyut:</span>
<span id="summary-est-size" class="summary-stat-value">0 MB</span>
</div>
<div class="summary-stat-item">
<span class="summary-stat-label" data-i18n="summary_est_time">Tahmini Süre:</span>
<span id="summary-est-time" class="summary-stat-value">~0 sn</span>
</div>
</div>
<!-- Fixed Bottom Panel for Formats and Download Action -->
<div class="fixed-footer glass-card">
<div class="footer-grid">
+10 -1
View File
@@ -61,5 +61,14 @@
"bulk_mp4": "تحويل المحدد إلى MP4",
"err_no_items_selected": "يرجى تحديد ملف واحد على الأقل للتنزيل.",
"playlist_title_label": "عنوان قائمة التشغيل:",
"downloaded_files_label": "الملفات التي تم تنزيلها:"
"downloaded_files_label": "الملفات التي تم تنزيلها:",
"summary_total_tracks": "إجمالي الملفات:",
"summary_total_duration": "المدة الإجمالية:",
"summary_speed_label": "السرعة:",
"summary_est_size": "الحجم المقدر:",
"summary_est_time": "الوقت المقدر:",
"hr_suffix": "ساعات",
"min_suffix_short": "د",
"sec_suffix_short": "ث",
"hr_suffix_short": "س"
}
+10 -1
View File
@@ -61,5 +61,14 @@
"bulk_mp4": "Ausgewählte in MP4",
"err_no_items_selected": "Bitte wählen Sie mindestens eine Datei zum Herunterladen aus.",
"playlist_title_label": "Wiedergabelisten-Titel:",
"downloaded_files_label": "Heruntergeladene Dateien:"
"downloaded_files_label": "Heruntergeladene Dateien:",
"summary_total_tracks": "Gesamte Dateien:",
"summary_total_duration": "Gesamte Dauer:",
"summary_speed_label": "Geschwindigkeit:",
"summary_est_size": "Schätz. Größe:",
"summary_est_time": "Schätz. Zeit:",
"hr_suffix": "Stunden",
"min_suffix_short": "Min.",
"sec_suffix_short": "Sek.",
"hr_suffix_short": "Std."
}
+10 -1
View File
@@ -61,5 +61,14 @@
"bulk_mp4": "Selected to MP4",
"err_no_items_selected": "Please select at least one file to download.",
"playlist_title_label": "Playlist Title:",
"downloaded_files_label": "Downloaded Files:"
"downloaded_files_label": "Downloaded Files:",
"summary_total_tracks": "Total Files:",
"summary_total_duration": "Total Duration:",
"summary_speed_label": "Internet Speed:",
"summary_est_size": "Est. Size:",
"summary_est_time": "Est. Time:",
"hr_suffix": "hours",
"min_suffix_short": "min",
"sec_suffix_short": "sec",
"hr_suffix_short": "hr"
}
+10 -1
View File
@@ -61,5 +61,14 @@
"bulk_mp4": "Выбранные в MP4",
"err_no_items_selected": "Пожалуйста, выберите хотя бы один файл для скачивания.",
"playlist_title_label": "Название плейлиста:",
"downloaded_files_label": "Скачанные файлы:"
"downloaded_files_label": "Скачанные файлы:",
"summary_total_tracks": "Всего файлов:",
"summary_total_duration": "Общая длительность:",
"summary_speed_label": "Скорость:",
"summary_est_size": "Оцен. размер:",
"summary_est_time": "Оцен. время:",
"hr_suffix": "ч.",
"min_suffix_short": "мин.",
"sec_suffix_short": "сек.",
"hr_suffix_short": "ч."
}
+10 -1
View File
@@ -61,5 +61,14 @@
"bulk_mp4": "Seçilenleri MP4 Yap",
"err_no_items_selected": "Lütfen indirmek için en az bir dosya seçin.",
"playlist_title_label": "Çalma Listesi:",
"downloaded_files_label": "İndirilen Dosyalar:"
"downloaded_files_label": "İndirilen Dosyalar:",
"summary_total_tracks": "Toplam Dosya:",
"summary_total_duration": "Toplam Süre:",
"summary_speed_label": "Hız:",
"summary_est_size": "Tahmini Boyut:",
"summary_est_time": "Tahmini Süre:",
"hr_suffix": "saat",
"min_suffix_short": "dk",
"sec_suffix_short": "sn",
"hr_suffix_short": "sa"
}
+109
View File
@@ -178,6 +178,13 @@ window.addEventListener('DOMContentLoaded', async () => {
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;
@@ -315,6 +322,95 @@ function updateSelectAllCheckboxState() {
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');
return;
}
// Calculate total count
document.getElementById('summary-total-count').textContent = checkedTracks.length;
// 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
@@ -322,6 +418,12 @@ backBtn.addEventListener('click', () => {
// 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;
@@ -382,6 +484,11 @@ function renderTrackList(info) {
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);
@@ -522,6 +629,8 @@ function renderTrackList(info) {
trackList.appendChild(itemEl);
});
updateSummaryStats();
}
// 8. Media Download Executor
+92
View File
@@ -1039,4 +1039,96 @@ html[dir="rtl"] .track-format-selector {
margin-left: 6px;
}
/* Playlist Summary Statistics Panel */
.playlist-summary-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
background: rgba(18, 14, 28, 0.35);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 14px;
margin-bottom: 12px;
gap: 12px;
flex-wrap: wrap;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
transition: all 0.3s ease;
}
.playlist-summary-card:hover {
border-color: rgba(255, 255, 255, 0.15);
background: rgba(18, 14, 28, 0.45);
}
.summary-stat-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.82rem;
}
.summary-stat-label {
color: #a49fc0;
font-weight: 500;
}
.summary-stat-value {
color: #00dfd8;
font-weight: 700;
text-shadow: 0 0 8px rgba(0, 223, 216, 0.15);
}
.summary-stat-speed-item {
gap: 6px;
}
.summary-speed-select {
background: rgba(12, 8, 20, 0.8);
color: #dcd7eb;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 6px;
padding: 2px 22px 2px 8px;
font-size: 0.78rem;
font-weight: 600;
outline: none;
cursor: pointer;
font-family: inherit;
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2300dfd8' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 6px center;
background-size: 8px;
transition: all 0.2s ease;
}
.summary-speed-select:hover {
border-color: #00dfd8;
background-color: rgba(12, 8, 20, 0.95);
box-shadow: 0 0 8px rgba(0, 223, 216, 0.2);
}
.summary-speed-select option {
background: #120e1c;
color: #fff;
}
/* RTL Support for Statistics Panel */
html[dir="rtl"] .playlist-summary-card {
flex-direction: row-reverse;
}
html[dir="rtl"] .summary-stat-item {
flex-direction: row-reverse;
}
html[dir="rtl"] .summary-speed-select {
padding: 2px 8px 2px 22px;
background-position: left 6px center;
}