Spaces:
Paused
Paused
| // مدیریت رابط کاربری (مدالها، شیتها، تغییر صفحات) | |
| 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); } |