268 lines
9.9 KiB
JavaScript
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…';
|
|
}
|
|
});
|
|
}
|
|
});
|