Zirnavis13 / static /js /editor.js
Elias207's picture
Create static/js/editor.js
f1befd3 verified
// منطق اصلی ادیتور (نمایش ویدیو، سینک متن، ابزارها)
function openProject(projectId) {
currentProjectId = projectId;
const tx = db.transaction("projects", "readonly");
const store = tx.objectStore("projects");
const req = store.get(projectId);
req.onsuccess = (e) => {
const p = e.target.result;
if (!p) { alert('پروژه یافت نشد'); loadHome(); return; }
state = p.state;
document.getElementById('currentProjectTitle').innerText = p.name;
const videoURL = URL.createObjectURL(p.videoBlob);
v.src = videoURL;
document.getElementById('col').value = state.st.col;
document.getElementById('bgCol').value = state.st.bg;
document.getElementById('fz').value = state.st.fz;
document.getElementById('pos').value = state.st.y;
document.querySelectorAll('.font-btn').forEach(btn => btn.classList.remove('ticked'));
if(state.st.f === 'vazir') document.querySelectorAll('.font-btn')[1].classList.add('ticked');
else if(state.st.f === 'lalezar') document.querySelectorAll('.font-btn')[0].classList.add('ticked');
document.querySelectorAll('.style-card').forEach(c => c.classList.remove('selected'));
if(state.st.name === 'karaoke_static') {
const staticCard = Array.from(document.querySelectorAll('.style-card')).find(c => c.textContent.includes("هوشمند (ثابت)"));
if(staticCard) staticCard.classList.add('selected');
document.getElementById('staticColorPicker').value = state.st.col;
}
document.getElementById('homeScreen').style.display = 'none';
document.getElementById('editorScreen').style.display = 'flex';
renderSegList();
fit();
upd();
v.onloadeddata = () => {
fit();
if(!p.thumbnail && v.duration > 1) {
v.currentTime = 1.0;
let captured = false;
v.addEventListener('seeked', function cap() {
if(captured) return; captured = true; saveProjectToDB(); v.currentTime = 0;
}, {once:true});
} else if (!p.thumbnail) { saveProjectToDB(); }
};
};
}
function goHome() { v.pause(); saveProjectToDB(); loadHome(); }
function fit() {
if(!state.w) return;
const ws = document.getElementById('workspace');
const availableHeight = window.innerHeight * 0.6;
const scale = Math.min((ws.clientWidth - 40) / state.w, availableHeight / state.h);
const c = document.getElementById('videoContainer');
c.style.width = state.w + 'px'; c.style.height = state.h + 'px';
document.getElementById('scaler').style.transform = `scale(${scale})`;
ws.style.height = (state.h * scale + 40) + 'px';
}
function activateCustomStyle() {
if(['karaoke_static', 'auto_director', 'plain_white', 'white_outline'].includes(state.st.name)) {
state.st.name = 'classic';
document.querySelectorAll('.style-card').forEach(c => c.classList.remove('selected'));
const classicCard = document.querySelector('.custom-style-container .style-card:first-child');
if(classicCard) classicCard.classList.add('selected');
}
}
function renderSegList() {
saveProjectToDB();
const timeline = document.getElementById('timelineScroll');
const spacers = timeline.querySelectorAll('.spacer');
timeline.innerHTML = '';
timeline.appendChild(spacers[0]); // Start spacer
state.segs.forEach((seg, sIdx) => {
if (!seg.words || seg.words.length === 0) {
const wordsArr = seg.text.trim().split(/\s+/).filter(w => w.length > 0);
const duration = seg.end - seg.start;
const timePerWord = duration / Math.max(1, wordsArr.length);
let wStart = seg.start;
seg.words = wordsArr.map((wStr, i) => {
let wEnd = wStart + timePerWord;
if (i === wordsArr.length - 1) wEnd = seg.end;
let obj = { word: wStr, start: parseFloat(wStart.toFixed(2)), end: parseFloat(wEnd.toFixed(2)) };
wStart = wEnd;
return obj;
});
}
seg.words.forEach((w, wIdx) => {
const el = document.createElement('div');
el.className = 'word-chip';
el.innerText = w.word;
const uid = `${sIdx}-${wIdx}`;
el.id = `w-${uid}`;
if (activeWordId === uid) el.classList.add('active');
el.onclick = (e) => {
e.stopPropagation();
highlightWord(sIdx, wIdx, true);
};
timeline.appendChild(el);
});
if (sIdx < state.segs.length - 1) {
const nl = document.createElement('div');
nl.className = 'newline-indicator';
nl.innerHTML = '<i class="fa-solid fa-arrow-turn-down" style="transform: rotate(90deg) scaleX(-1);"></i>';
timeline.appendChild(nl);
}
});
timeline.appendChild(spacers[1]); // End spacer
updateSplitButton();
}
function highlightWord(sIdx, wIdx, showToolbar) {
activeWordId = `${sIdx}-${wIdx}`;
v.pause();
togglePlayIcon(false);
const seg = state.segs[sIdx];
const word = seg.words[wIdx];
v.currentTime = word.start;
manualOverride = true;
updateOverlayContent(v.currentTime);
document.querySelectorAll('.word-chip').forEach(c => c.classList.remove('active'));
const el = document.getElementById(`w-${activeWordId}`);
if(el) {
el.classList.add('active');
el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
}
if(showToolbar) document.getElementById('toolbar').classList.add('show');
updateSplitButton();
}
function playPreviewWord() {
if (!activeWordId) return;
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
const word = state.segs[sIdx].words[wIdx];
if(previewInterval) clearInterval(previewInterval);
v.pause();
v.currentTime = word.start;
const icon = document.getElementById('btnPreviewPlay').querySelector('i');
icon.className = "fa-solid fa-pause";
v.play();
previewInterval = setInterval(() => {
if(v.currentTime >= word.end) {
v.pause();
clearInterval(previewInterval);
icon.className = "fa-solid fa-play";
if (v.currentTime >= word.end) v.currentTime = word.end - 0.01;
}
}, 20);
}
function saveText() {
if (!activeWordId) return;
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
const val = document.getElementById('textInput').value.trim();
if (val) {
state.segs[sIdx].words[wIdx].word = val;
state.segs[sIdx].text = state.segs[sIdx].words.map(w => w.word).join(' ');
renderSegList();
highlightWord(sIdx, wIdx, true);
}
closeAllSheets();
}
function adjustTime(type, am) {
if(type === 'start') {
let v = parseFloat((tempStartTime + am).toFixed(2));
if(v < 0) v = 0;
if(v >= tempEndTime) v = parseFloat((tempEndTime - 0.1).toFixed(2));
tempStartTime = v;
document.getElementById('valStart').innerText = fmt(tempStartTime);
} else {
let v = parseFloat((tempEndTime + am).toFixed(2));
if(v <= tempStartTime) v = parseFloat((tempStartTime + 0.1).toFixed(2));
tempEndTime = v;
document.getElementById('valEnd').innerText = fmt(tempEndTime);
}
}
function confirmTimeChanges() {
if (!activeWordId) return;
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
const seg = state.segs[sIdx];
seg.words[wIdx].start = tempStartTime;
seg.words[wIdx].end = tempEndTime;
if (wIdx === 0 && tempStartTime < seg.start) seg.start = tempStartTime;
if (wIdx === seg.words.length - 1 && tempEndTime > seg.end) seg.end = tempEndTime;
closeAllSheets();
renderSegList();
highlightWord(sIdx, wIdx, true);
}
function cancelTimeChanges() { closeAllSheets(); }
function confirmDelete() {
if (!activeWordId) return;
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
const seg = state.segs[sIdx];
seg.words.splice(wIdx, 1);
if (seg.words.length === 0) {
state.segs.splice(sIdx, 1);
activeWordId = null;
document.getElementById('toolbar').classList.remove('show');
} else {
seg.text = seg.words.map(w => w.word).join(' ');
activeWordId = null;
document.getElementById('toolbar').classList.remove('show');
}
closeAllSheets();
renderSegList();
}
function toggleSplit() {
if (!activeWordId) return;
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
if (wIdx === 0) {
if (sIdx > 0) {
const prevSeg = state.segs[sIdx - 1];
const currSeg = state.segs[sIdx];
prevSeg.words = prevSeg.words.concat(currSeg.words);
prevSeg.end = currSeg.end;
prevSeg.text = prevSeg.words.map(w => w.word).join(' ');
state.segs.splice(sIdx, 1);
const newWIdx = prevSeg.words.length - currSeg.words.length;
renderSegList();
highlightWord(sIdx - 1, newWIdx, true);
}
} else {
const seg = state.segs[sIdx];
const wordsFirstHalf = seg.words.slice(0, wIdx);
const wordsSecondHalf = seg.words.slice(wIdx);
seg.words = wordsFirstHalf;
seg.text = seg.words.map(w => w.word).join(' ');
seg.end = wordsFirstHalf[wordsFirstHalf.length-1].end;
const newSeg = {
text: wordsSecondHalf.map(w => w.word).join(' '),
start: wordsSecondHalf[0].start,
end: wordsSecondHalf[wordsSecondHalf.length-1].end,
words: wordsSecondHalf,
isHidden: false
};
state.segs.splice(sIdx + 1, 0, newSeg);
renderSegList();
highlightWord(sIdx + 1, 0, true);
}
}
function updateSplitButton() {
const btn = document.getElementById('btnSplit');
if(!activeWordId) { btn.classList.remove('active-state'); return; }
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
if (wIdx === 0 && sIdx > 0) {
btn.classList.add('active-state');
btn.style.transform = "rotate(180deg)";
} else {
btn.classList.remove('active-state');
btn.style.transform = "";
}
}
function togglePlay() { if(v.paused) { v.play(); togglePlayIcon(true); } else { v.pause(); togglePlayIcon(false); } }
function togglePlayIcon(isPlaying) { const overlay = document.getElementById('playOverlay'); overlay.className = isPlaying ? 'playing' : ''; }
function updateOverlayContent(currentTime) {
const idx = state.segs.findIndex(s => currentTime >= s.start && currentTime <= s.end);
if(idx !== -1) {
const seg = state.segs[idx];
if(seg.isHidden) { tEl.style.opacity = 0; }
else {
tEl.style.opacity = 1;
if(state.st.name === 'auto_director' && seg.words) {
let html = ""; seg.words.forEach((w, i) => {
let isActive = (currentTime >= w.start && currentTime <= w.end);
let boxColor = (i % 2 === 0) ? '#00D7FF' : '#FF0080';
if(isActive) html += `<span style="background-color: ${boxColor}; color: #ffffff !important; box-shadow: 0 0 15px ${boxColor}; transform: scale(1.1); display:inline-block; border-radius: 6px; padding: 0 6px; text-shadow:none; font-family: inherit;">${w.word}</span> `;
else html += `<span style="color: #ffffff !important; text-shadow:none; font-family: inherit;">${w.word}</span> `;
}); tEl.innerHTML = html;
}
else if(state.st.name === 'karaoke_static' && seg.words) {
let html = ""; seg.words.forEach(w => {
let isActive = (currentTime >= w.start && currentTime <= w.end); let cls = isActive ? "word-active" : "";
let styleAttr = ""; if(isActive) { let boxColor = state.st.col; styleAttr = `style="background-color: ${boxColor} !important; color: #fff !important; box-shadow: 0 2px 8px ${boxColor};"`; }
html += `<span class="${cls}" ${styleAttr}>${w.word}</span> `;
}); tEl.innerHTML = html;
}
else if (state.st.name === 'progressive_write' && seg.words) {
let html = ""; seg.words.forEach(w => { if(currentTime >= w.start) html += `<span style="opacity:1">${w.word}</span> `; else html += `<span style="opacity:0">${w.word}</span> `; }); tEl.innerHTML = html.trim();
}
else { tEl.innerText = seg.text; }
}
} else { tEl.style.opacity = 0; }
}
function upd() {
saveProjectToDB();
state.st.fz = parseInt(document.getElementById('fz').value);
state.st.y = parseInt(document.getElementById('pos').value);
if (state.st.name !== 'karaoke_static') state.st.col = document.getElementById('col').value;
state.st.bg = document.getElementById('bgCol').value;
let font = 'Vazirmatn';
if(state.st.f === 'lalezar') font = 'Lalezar'; if(state.st.f === 'bangers') font = 'Impact'; if(state.st.f === 'roboto') font = 'Arial';
tEl.style.fontFamily = font; tEl.style.fontSize = state.st.fz + 'px'; tEl.style.bottom = state.st.y + 'px'; tEl.style.textAlign = 'center'; tEl.style.left = '50%'; tEl.style.transform = `translateX(calc(-50% + ${state.st.x}px))`;
tEl.style.paintOrder = 'normal'; tEl.style.webkitPaintOrder = 'normal'; tEl.style.borderRadius = '0px';
if(state.st.name === 'karaoke_static' || state.st.name === 'auto_director') {
tEl.style.backgroundColor = 'transparent'; tEl.style.color = '#FFFFFF'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none';
} else if (state.st.name === 'plain_white') {
tEl.style.color = '#FFFFFF'; tEl.style.backgroundColor = 'transparent'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none';
} else if (state.st.name === 'white_outline') {
tEl.style.color = '#FFFFFF'; tEl.style.backgroundColor = 'transparent'; const s = Math.max(3, state.st.fz / 4.5); tEl.style.webkitTextStroke = `${s}px #000000`; tEl.style.paintOrder = 'stroke fill'; tEl.style.webkitPaintOrder = 'stroke fill'; tEl.style.textShadow = 'none';
} else {
tEl.style.color = state.st.col;
if(state.st.type === 'solid') { tEl.style.backgroundColor = state.st.bg; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; tEl.style.borderRadius = '12px'; }
else if (state.st.type === 'transparent') {
let c = state.st.bg.replace('#', ''); let r = parseInt(c.substring(0, 2), 16); let g = parseInt(c.substring(2, 4), 16); let b = parseInt(c.substring(4, 6), 16);
tEl.style.backgroundColor = `rgba(${r},${g},${b},0.6)`; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; tEl.style.borderRadius = '12px';
} else if (state.st.type === 'outline') {
tEl.style.backgroundColor = 'transparent'; tEl.style.color = state.st.col; const s = Math.max(3, state.st.fz / 4.5); tEl.style.webkitTextStroke = `${s}px ${state.st.bg}`; tEl.style.paintOrder = 'stroke fill'; tEl.style.webkitPaintOrder = 'stroke fill'; tEl.style.textShadow = 'none';
} else { tEl.style.backgroundColor = 'transparent'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; }
}
}
function openStaticColorPicker(event) { event.stopPropagation(); document.getElementById('staticColorPicker').click(); }
function updateStaticColor(val) { document.documentElement.style.setProperty('--static-color', val); state.st.col = val; document.getElementById('col').value = val; upd(); }
function setStylePreset(name, el, skipModeSet = false) {
state.st.name = name;
document.querySelectorAll('.style-card').forEach(c => c.classList.remove('selected'));
if(el) el.classList.add('selected');
if(name === 'karaoke_static' && !skipModeSet) {
const defaultPurple = '#A020F0';
state.st.col = defaultPurple;
document.documentElement.style.setProperty('--static-color', defaultPurple);
document.getElementById('staticColorPicker').value = defaultPurple;
document.getElementById('col').value = defaultPurple;
}
if(name === 'classic' && !skipModeSet) {
setMode('solid');
} else if (name === 'progressive_write') {
setMode('none');
} else if (name === 'plain_white' || name === 'white_outline') {
state.st.col = '#FFFFFF';
state.st.bg = '#000000';
document.getElementById('col').value = '#FFFFFF';
document.getElementById('bgCol').value = '#000000';
setMode('outline');
}
upd();
}
function handleClassicDoubleClick(element) {
event.stopPropagation();
activateCustomStyle();
setStylePreset('classic', element, true);
setMode('none');
}
function setFont(f, el) { document.querySelectorAll('.font-btn').forEach(btn => btn.classList.remove('ticked')); el.classList.add('ticked'); state.st.f = f; upd(); }
function setMode(m) { state.st.type = m; syncModeButtons(); upd(); }