2026-04-19 01:12:47 +00:00

268 lines
9.9 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('converter-form');
const input = document.getElementById('source_file') || document.getElementById('video_file');
const selectedFile = document.getElementById('selected-file');
const overlay = document.getElementById('loading-overlay');
const toastEl = document.getElementById('appToast');
const submitButton = document.getElementById('submit-button');
const themeToggle = document.getElementById('theme-toggle');
const themeLabel = document.getElementById('theme-toggle-label');
const themeIcon = document.getElementById('theme-toggle-icon');
const toolSelect = document.getElementById('tool_key');
const toolDescription = document.getElementById('tool-description');
const toolRuntimeNote = document.getElementById('tool-runtime-note');
const presetGroup = document.getElementById('preset-group');
const subtitleTargetGroup = document.getElementById('subtitle-target-group');
const dropzone = document.getElementById('file-dropzone');
const fileInputTitle = document.getElementById('file-input-title');
const fileInputCopy = document.getElementById('file-input-copy');
const fileInputMeta = document.getElementById('file-input-meta');
const appState = document.body?.dataset?.appState || 'ready';
const uploadLimitMb = Number(document.body?.dataset?.uploadLimitMb || 0);
const toolConfig = window.formatShiftTools || {};
const applyTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.setAttribute('data-bs-theme', theme);
if (themeToggle) {
themeToggle.setAttribute('aria-pressed', theme === 'dark' ? 'true' : 'false');
}
if (themeLabel) {
themeLabel.textContent = theme === 'dark' ? 'Light mode' : 'Dark mode';
}
if (themeIcon) {
themeIcon.textContent = theme === 'dark' ? '☀️' : '🌙';
}
};
let currentTheme = 'light';
try {
const savedTheme = localStorage.getItem('fs-theme');
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
currentTheme = savedTheme || (prefersDark ? 'dark' : 'light');
} catch (error) {
currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
}
applyTheme(currentTheme);
if (themeToggle) {
themeToggle.addEventListener('click', () => {
currentTheme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
applyTheme(currentTheme);
try {
localStorage.setItem('fs-theme', currentTheme);
} catch (error) {
// Ignore storage failures and keep the in-memory theme.
}
});
}
const currentTool = () => {
if (!toolSelect) {
return null;
}
return toolConfig[toolSelect.value] || null;
};
const setDropzoneState = (state) => {
if (!dropzone) {
return;
}
dropzone.classList.toggle('is-dragging', state === 'dragging');
dropzone.classList.toggle('is-has-file', state === 'has-file');
};
const assignFiles = (files) => {
if (!input || !files || files.length === 0) {
return false;
}
if (typeof DataTransfer === 'undefined') {
return false;
}
const transfer = new DataTransfer();
Array.from(files).slice(0, 1).forEach((file) => transfer.items.add(file));
input.files = transfer.files;
return true;
};
const renderSelectedFile = () => {
if (!input || !selectedFile) {
return;
}
const file = input.files && input.files[0] ? input.files[0] : null;
if (!file) {
selectedFile.classList.add('d-none');
selectedFile.classList.remove('selected-file-warning');
selectedFile.textContent = '';
setDropzoneState('idle');
return;
}
const config = currentTool();
const sizeMb = file.size / (1024 * 1024);
const sizeLabel = sizeMb.toFixed(2);
const extension = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : '';
const allowedExtensions = (config?.accept || '').split(',').map((item) => item.replace('.', '').trim()).filter(Boolean);
const badExtension = allowedExtensions.length > 0 && extension !== '' && !allowedExtensions.includes(extension);
const exceedsLimit = uploadLimitMb > 0 && sizeMb > uploadLimitMb;
selectedFile.innerHTML = '';
const name = document.createElement('span');
name.className = 'selected-file-name';
name.textContent = file.name;
const meta = document.createElement('span');
meta.className = 'selected-file-meta';
if (badExtension) {
meta.textContent = `${sizeLabel} MB selected · Not supported for the current converter`;
} else if (exceedsLimit) {
meta.textContent = `${sizeLabel} MB selected · Above the ${uploadLimitMb} MB limit`;
} else {
meta.textContent = `${sizeLabel} MB selected · Ready for ${config?.label || 'conversion'}`;
}
selectedFile.appendChild(name);
selectedFile.appendChild(meta);
selectedFile.classList.toggle('selected-file-warning', badExtension || exceedsLimit);
selectedFile.classList.remove('d-none');
setDropzoneState('has-file');
if (fileInputTitle) {
fileInputTitle.textContent = `Replace file for ${config?.label || 'this converter'}`;
}
};
const syncToolUi = () => {
const config = currentTool();
if (!config) {
renderSelectedFile();
return;
}
if (input) {
input.setAttribute('accept', config.accept || '');
}
if (toolDescription) {
toolDescription.textContent = config.description || '';
}
if (toolRuntimeNote) {
if (config.requiresFfmpeg && appState !== 'ready') {
toolRuntimeNote.textContent = 'This tool needs FFmpeg, which is currently unavailable on the server.';
} else if (config.requiresFfmpeg) {
toolRuntimeNote.textContent = 'This tool runs through the server FFmpeg pipeline.';
} else {
toolRuntimeNote.textContent = 'This tool runs without FFmpeg, so it works even if the video runtime is offline.';
}
}
if (presetGroup) {
presetGroup.classList.toggle('d-none', !config.presets || Object.keys(config.presets).length === 0);
}
if (subtitleTargetGroup) {
subtitleTargetGroup.classList.toggle('d-none', toolSelect?.value !== 'subtitle_convert');
}
if (fileInputTitle && !(input?.files && input.files[0])) {
fileInputTitle.textContent = `Drop a file for ${config.label}`;
}
if (fileInputCopy) {
fileInputCopy.textContent = config.acceptSummary || 'Choose a supported file to continue.';
}
if (fileInputMeta) {
const limitCopy = uploadLimitMb > 0 ? `Up to ${uploadLimitMb} MB` : 'Any file size supported by the server';
fileInputMeta.textContent = `or click to browse from your device · ${limitCopy}`;
}
if (submitButton) {
submitButton.dataset.defaultLabel = config.submitLabel || 'Convert file';
submitButton.textContent = config.submitLabel || 'Convert file';
}
renderSelectedFile();
};
if (toastEl && window.bootstrap) {
const toastBody = toastEl.querySelector('.toast-body');
if (toastBody) {
const config = currentTool();
if (appState === 'ready') {
toastBody.textContent = config ? `Ready for ${config.label}.` : 'Ready to convert files.';
} else {
toastBody.textContent = 'Video tools are paused because FFmpeg is unavailable, but subtitle conversion still works.';
}
}
const toast = new bootstrap.Toast(toastEl);
toast.show();
}
if (toolSelect) {
toolSelect.addEventListener('change', syncToolUi);
syncToolUi();
}
if (input && selectedFile) {
input.addEventListener('change', renderSelectedFile);
}
if (dropzone && input) {
['dragenter', 'dragover'].forEach((eventName) => {
dropzone.addEventListener(eventName, (event) => {
event.preventDefault();
event.stopPropagation();
setDropzoneState('dragging');
});
});
['dragleave', 'dragend'].forEach((eventName) => {
dropzone.addEventListener(eventName, (event) => {
event.preventDefault();
event.stopPropagation();
setDropzoneState(input.files && input.files[0] ? 'has-file' : 'idle');
});
});
dropzone.addEventListener('drop', (event) => {
event.preventDefault();
event.stopPropagation();
const files = event.dataTransfer?.files;
const assigned = assignFiles(files);
setDropzoneState(assigned ? 'has-file' : 'idle');
if (assigned) {
renderSelectedFile();
}
});
dropzone.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
input.click();
}
});
}
if (form && overlay) {
form.addEventListener('submit', () => {
overlay.classList.remove('d-none');
overlay.setAttribute('aria-hidden', 'false');
if (submitButton) {
submitButton.disabled = true;
submitButton.textContent = 'Uploading and processing…';
}
});
}
});