feat: implement UI layout and internationalization support for YouTube downloader
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user