| |
|
|
| 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"); |
| |
| |
| 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'); |
| |
| |
| 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; |
| } |
|
|
| |
| 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); } |