Zirnavis / static /js /ui.js
Opera8's picture
Update static/js/ui.js
ab1011d verified
Raw
History Blame Contribute Delete
12.7 kB
// مدیریت رابط کاربری (مدال‌ها، شیت‌ها، تغییر صفحات)
async function loadHome() {
document.getElementById('homeScreen').style.display = 'flex';
document.getElementById('editorScreen').style.display = 'none';
if (!db) {
try { await initDB(); } catch(e) { console.error("DB Init Failed inside loadHome"); return; }
}
const tx = db.transaction("projects", "readonly");
const store = tx.objectStore("projects");
const req = store.getAll();
req.onsuccess = (e) => {
let projects = e.target.result;
projects.sort((a, b) => b.id - a.id);
const grid = document.getElementById('projectsGrid');
const placeholder = document.getElementById('noProjectsPlaceholder');
if (projects.length === 0) {
grid.innerHTML = '';
placeholder.style.display = 'flex';
} else {
placeholder.style.display = 'none';
grid.innerHTML = '';
projects.forEach((p, index) => {
const card = document.createElement('div');
card.className = 'project-card';
card.onclick = () => openProject(p.id);
const thumbUrl = p.thumbnail ? `url(${p.thumbnail})` : 'none';
card.innerHTML = `
<div class="card-thumb" style="background-image: ${thumbUrl}">
<div class="card-overlay"></div>
<div class="card-menu" onclick="openActionSheet(${p.id}, '${p.name}', event)"><i class="fa-solid fa-ellipsis"></i></div>
<div class="card-duration">${p.duration || '00:00'}</div>
</div>
<div class="card-details">
<div class="card-title">${p.name}</div>
<div class="card-date">${p.dateStr}</div>
</div>
`;
grid.appendChild(card);
});
}
};
}
function openActionSheet(id, name, event) {
event.stopPropagation();
currentActionProjectId = id;
currentActionProjectName = name;
const sheet = document.getElementById('projectActionOverlay');
sheet.style.display = 'flex';
}
function closeActionSheet() {
const sheet = document.getElementById('projectActionOverlay');
sheet.style.display = 'none';
}
function openDeleteConfirmation() {
closeActionSheet();
showConfirmationModal({
iconClass: 'fa-solid fa-trash-can',
title: 'حذف پروژه',
description: `آیا از حذف کامل «${currentActionProjectName}» مطمئن هستید؟`,
confirmText: 'بله، حذف کن',
confirmClass: 'delete',
onConfirm: () => {
const tx = db.transaction("projects", "readwrite");
const store = tx.objectStore("projects");
// ابتدا دریافت پروژه از دیتابیس مرورگر برای پیدا کردن شناسه سرور (file_id)
store.get(currentActionProjectId).onsuccess = (e) => {
const project = e.target.result;
if (project && project.state && project.state.id) {
const fileId = project.state.id;
// ارسال درخواست حذف به سرور در پس‌زمینه (بدون معطل کردن کاربر)
fetch(`/api/delete-project/${fileId}`, { method: 'DELETE' })
.catch(err => console.error("Failed to delete project files on server:", err));
}
// حذف پروژه از دیتابیس محلی مرورگر
store.delete(currentActionProjectId);
};
tx.oncomplete = () => loadHome();
}
});
}
function openRenameModal() {
closeActionSheet();
const modal = document.getElementById('renameModal');
const input = document.getElementById('renameInput');
input.value = currentActionProjectName;
modal.style.display = 'flex';
setTimeout(() => modal.classList.add('show'), 10);
}
function closeRenameModal() {
const modal = document.getElementById('renameModal');
modal.classList.remove('show');
setTimeout(() => modal.style.display = 'none', 300);
}
function saveRename() {
const newName = document.getElementById('renameInput').value.trim();
if (newName && currentActionProjectId) {
const tx = db.transaction("projects", "readwrite");
const store = tx.objectStore("projects");
store.get(currentActionProjectId).onsuccess = (e) => {
const project = e.target.result;
if (project) {
project.name = newName;
store.put(project);
}
};
tx.oncomplete = () => {
closeRenameModal();
loadHome();
};
} else {
closeRenameModal();
}
}
function toggleTool(toolName) {
document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active-tool'));
const btnId = 'btn-' + toolName;
const btn = document.getElementById(btnId);
const sectionId = 'section-' + toolName;
const section = document.getElementById(sectionId);
const isAlreadyOpen = toolsContainer.classList.contains('open') && section.style.display === 'block';
if (isAlreadyOpen) {
toolsContainer.classList.remove('open');
setTimeout(() => { section.style.display = 'none'; }, 200);
} else {
document.querySelectorAll('.tool-section').forEach(s => s.style.display = 'none');
if(btn) btn.classList.add('active-tool');
section.style.display = 'block';
section.classList.add('active-section');
toolsContainer.classList.add('open');
if(toolName === 'text') {
toolsContainer.style.maxHeight = 'none';
renderSegList();
if (state.segs && state.segs.length > 0) {
let sIdxToSelect = 0;
const curTime = v.currentTime;
const currentSegIdx = state.segs.findIndex(s => curTime >= s.start && curTime <= s.end);
if (currentSegIdx !== -1) sIdxToSelect = currentSegIdx;
else {
const nextSegIdx = state.segs.findIndex(s => s.start > curTime);
if (nextSegIdx !== -1) sIdxToSelect = nextSegIdx;
}
setTimeout(() => {
if (state.segs[sIdxToSelect] && state.segs[sIdxToSelect].words.length > 0) {
highlightWord(sIdxToSelect, 0, true);
}
}, 50);
}
} else {
toolsContainer.style.maxHeight = '';
}
}
}
function toggleCustomAccordion() { document.getElementById('customAccordion').classList.toggle('open'); }
function closeAllSheets() {
const overlay = document.getElementById('sheetOverlay');
overlay.classList.remove('show');
document.querySelectorAll('.bottom-sheet').forEach(s => {
s.classList.remove('active');
});
document.getElementById('textInput').blur();
if(previewInterval) clearInterval(previewInterval);
v.pause();
const icon1 = document.getElementById('btnPreviewPlay')?.querySelector('i');
if(icon1) icon1.className = "fa-solid fa-play";
const icon2 = document.getElementById('btnTimeSheetPlayIcon');
if(icon2) icon2.className = "fa-solid fa-play";
}
function openSheet(type) {
if (!activeWordId && type !== 'regenerate' && type !== 'wordcount') return;
closeAllSheets();
const targetSheet = document.getElementById(`sheet-${type}`);
const overlay = document.getElementById('sheetOverlay');
if(type === 'text') {
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
const seg = state.segs[sIdx];
const word = seg.words[wIdx];
document.getElementById('textInput').value = word.word;
document.getElementById('textTimeDisplay').innerText = `${fmt(word.start)} -> ${fmt(word.end)}`;
} else if(type === 'time') {
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
const seg = state.segs[sIdx];
const word = seg.words[wIdx];
tempStartTime = word.start;
tempEndTime = word.end;
document.getElementById('timeSheetWord').innerText = word.word;
const prevBox = document.getElementById('prevWordPreview');
const nextBox = document.getElementById('nextWordPreview');
// محاسبه محدوده مجاز (Boundary)
let minAllowed = 0;
let maxAllowed = v.duration || 1000;
// محاسبه کلمه قبل و حد پایین
if (wIdx > 0) {
prevBox.innerText = seg.words[wIdx - 1].word;
minAllowed = seg.words[wIdx - 1].end;
} else if (sIdx > 0 && state.segs[sIdx-1].words.length > 0) {
const prevSeg = state.segs[sIdx-1];
prevBox.innerText = prevSeg.words[prevSeg.words.length - 1].word;
minAllowed = prevSeg.words[prevSeg.words.length - 1].end;
} else {
prevBox.innerText = "-";
minAllowed = 0;
}
// محاسبه کلمه بعد و حد بالا
if (wIdx < seg.words.length - 1) {
nextBox.innerText = seg.words[wIdx + 1].word;
maxAllowed = seg.words[wIdx + 1].start;
} else if (sIdx < state.segs.length - 1 && state.segs[sIdx+1].words.length > 0) {
nextBox.innerText = state.segs[sIdx+1].words[0].word;
maxAllowed = state.segs[sIdx+1].words[0].start;
} else {
nextBox.innerText = "-";
maxAllowed = v.duration || tempEndTime + 5;
}
// راه‌اندازی Trimmer با محدوده‌های دقیق
if(typeof initTrimmerUI === 'function') {
initTrimmerUI(tempStartTime, tempEndTime, minAllowed, maxAllowed);
}
} else if(type === 'delete') {
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
const seg = state.segs[sIdx];
const word = seg.words[wIdx];
document.getElementById('deleteMsgText').innerHTML = `آیا کلمه <span class="highlight-word">«${word.word}»</span> حذف شود؟`;
} else if (type === 'regenerate') {
if (typeof isRegenerating !== 'undefined' && isRegenerating) {
document.getElementById('regen-step-start').style.display = 'none';
document.getElementById('regen-step-loading').style.display = 'flex';
document.getElementById('regen-step-success').style.display = 'none';
} else {
document.getElementById('regen-step-start').style.display = 'flex';
document.getElementById('regen-step-loading').style.display = 'none';
document.getElementById('regen-step-success').style.display = 'none';
}
}
setTimeout(() => {
overlay.classList.add('show');
targetSheet.classList.add('active');
}, 50);
}
function preConfirmRegenerate() {
showConfirmationModal({
iconClass: 'fa-solid fa-rotate',
title: 'بازنویسی هوشمند',
description: 'آیا می‌خواهید هوش مصنوعی دوباره ویدیو را بررسی کرده و زیرنویس‌ها را از نو بنویسد؟ (زیرنویس‌های فعلی حذف می‌شوند)',
confirmText: 'بله، بازنویسی کن',
confirmClass: 'split',
onConfirm: () => {
regenerateProjectSubtitles();
}
});
}
function showConfirmationModal(config) {
const modal = document.getElementById('confirmationModal');
document.getElementById('confirmIcon').querySelector('i').className = config.iconClass;
document.getElementById('confirmTitle').innerText = config.title;
document.getElementById('confirmDesc').innerText = config.description;
const btn = document.getElementById('confirmBtn');
btn.innerText = config.confirmText; btn.className = 'modal-btn btn-confirm-action ' + config.confirmClass;
btn.onclick = () => { config.onConfirm(); closeConfirmationModal(); };
modal.style.display = 'flex'; setTimeout(() => modal.classList.add('show'), 10);
}
function closeConfirmationModal() { const m = document.getElementById('confirmationModal'); m.classList.remove('show'); setTimeout(() => m.style.display = 'none', 300); }