Spaces:
Sleeping
Sleeping
Update static/js/editor.js
Browse files- static/js/editor.js +81 -49
static/js/editor.js
CHANGED
|
@@ -210,77 +210,105 @@ function highlightWord(sIdx, wIdx, showToolbar) {
|
|
| 210 |
// ============================================
|
| 211 |
|
| 212 |
function initTrimmerUI(startT, endT, minLimit, maxLimit) {
|
| 213 |
-
// تنظیم محدودیتهای سراسری
|
| 214 |
trimMinLimit = minLimit;
|
| 215 |
trimMaxLimit = maxLimit;
|
| 216 |
trimViewDuration = trimMaxLimit - trimMinLimit;
|
| 217 |
|
| 218 |
-
//
|
| 219 |
-
if(trimViewDuration <= 0.
|
| 220 |
|
| 221 |
// اعمال مقادیر اولیه به UI
|
| 222 |
updateTrimmerVisuals();
|
| 223 |
|
| 224 |
-
//
|
| 225 |
const handleL = document.getElementById('handleLeft');
|
| 226 |
const handleR = document.getElementById('handleRight');
|
| 227 |
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
|
|
|
| 240 |
};
|
| 241 |
|
| 242 |
-
|
| 243 |
-
if(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
}
|
| 245 |
|
| 246 |
function onDrag(e) {
|
| 247 |
if (!activeDragHandle) return;
|
| 248 |
-
|
|
|
|
|
|
|
| 249 |
e.stopPropagation();
|
| 250 |
|
| 251 |
const strip = document.getElementById('timelineStrip');
|
| 252 |
if (!strip) return;
|
| 253 |
|
| 254 |
const rect = strip.getBoundingClientRect();
|
| 255 |
-
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
| 256 |
|
| 257 |
-
//
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
let percent = (clientX - rect.left) / rect.width;
|
|
|
|
|
|
|
| 260 |
percent = Math.max(0, Math.min(1, percent));
|
| 261 |
|
| 262 |
// تبدیل درصد به زمان واقعی
|
| 263 |
let newTime = trimMinLimit + (percent * trimViewDuration);
|
| 264 |
|
|
|
|
|
|
|
|
|
|
| 265 |
if (activeDragHandle === 'left') {
|
| 266 |
-
//
|
| 267 |
-
// نباید
|
| 268 |
-
// نباید
|
| 269 |
if (newTime >= tempEndTime - 0.05) newTime = tempEndTime - 0.05;
|
|
|
|
| 270 |
|
| 271 |
-
tempStartTime =
|
| 272 |
} else {
|
| 273 |
-
//
|
| 274 |
-
// نباید
|
|
|
|
| 275 |
if (newTime <= tempStartTime + 0.05) newTime = tempStartTime + 0.05;
|
|
|
|
| 276 |
|
| 277 |
-
tempEndTime =
|
| 278 |
}
|
| 279 |
|
|
|
|
| 280 |
updateTrimmerVisuals();
|
| 281 |
}
|
| 282 |
|
| 283 |
-
function endDrag() {
|
|
|
|
|
|
|
|
|
|
| 284 |
activeDragHandle = null;
|
| 285 |
document.removeEventListener('mousemove', onDrag);
|
| 286 |
document.removeEventListener('mouseup', endDrag);
|
|
@@ -290,23 +318,29 @@ function endDrag() {
|
|
| 290 |
|
| 291 |
function updateTrimmerVisuals() {
|
| 292 |
// نمایش متنی زمان
|
| 293 |
-
document.getElementById('trimStartDisp')
|
| 294 |
-
document.getElementById('trimEndDisp')
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
-
// محاسبه
|
| 297 |
-
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
const handleL = document.getElementById('handleLeft');
|
| 301 |
const handleR = document.getElementById('handleRight');
|
| 302 |
const track = document.getElementById('trackActive');
|
| 303 |
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
// نوار رنگی وسط از موقعیت چپ شروع میشه و عرضش تفاضل دو درصد است
|
| 310 |
track.style.left = `${leftP}%`;
|
| 311 |
track.style.width = `${rightP - leftP}%`;
|
| 312 |
}
|
|
@@ -321,13 +355,12 @@ function formatTimeMs(t) {
|
|
| 321 |
|
| 322 |
// تابع پخش دقیق بازه انتخاب شده
|
| 323 |
function playPreviewWord() {
|
| 324 |
-
if (
|
| 325 |
|
| 326 |
-
// توقف پخش قبلی
|
| 327 |
if(previewInterval) clearInterval(previewInterval);
|
| 328 |
v.pause();
|
| 329 |
|
| 330 |
-
//
|
| 331 |
v.currentTime = tempStartTime;
|
| 332 |
|
| 333 |
const icon1 = document.getElementById('btnPreviewPlay')?.querySelector('i');
|
|
@@ -338,7 +371,7 @@ function playPreviewWord() {
|
|
| 338 |
|
| 339 |
v.play().then(() => {
|
| 340 |
previewInterval = setInterval(() => {
|
| 341 |
-
// اگر
|
| 342 |
if(v.currentTime >= tempEndTime) {
|
| 343 |
v.pause();
|
| 344 |
clearInterval(previewInterval);
|
|
@@ -346,8 +379,8 @@ function playPreviewWord() {
|
|
| 346 |
if(icon1) icon1.className = "fa-solid fa-play";
|
| 347 |
if(icon2) icon2.className = "fa-solid fa-play";
|
| 348 |
|
| 349 |
-
// برگرداندن
|
| 350 |
-
v.currentTime = tempEndTime
|
| 351 |
}
|
| 352 |
}, 15);
|
| 353 |
}).catch(e => console.log("Play interrupted", e));
|
|
@@ -386,11 +419,10 @@ function confirmTimeChanges() {
|
|
| 386 |
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
|
| 387 |
const seg = state.segs[sIdx];
|
| 388 |
|
| 389 |
-
// اعمال
|
| 390 |
seg.words[wIdx].start = tempStartTime;
|
| 391 |
seg.words[wIdx].end = tempEndTime;
|
| 392 |
|
| 393 |
-
// اصلاح مرزهای سگمنت والد
|
| 394 |
if (wIdx === 0 && tempStartTime < seg.start) seg.start = tempStartTime;
|
| 395 |
if (wIdx === seg.words.length - 1 && tempEndTime > seg.end) seg.end = tempEndTime;
|
| 396 |
|
|
|
|
| 210 |
// ============================================
|
| 211 |
|
| 212 |
function initTrimmerUI(startT, endT, minLimit, maxLimit) {
|
| 213 |
+
// تنظیم محدودیتهای سراسری
|
| 214 |
trimMinLimit = minLimit;
|
| 215 |
trimMaxLimit = maxLimit;
|
| 216 |
trimViewDuration = trimMaxLimit - trimMinLimit;
|
| 217 |
|
| 218 |
+
// جلوگیری از خطای تقسیم بر صفر
|
| 219 |
+
if(trimViewDuration <= 0.001) trimViewDuration = 1;
|
| 220 |
|
| 221 |
// اعمال مقادیر اولیه به UI
|
| 222 |
updateTrimmerVisuals();
|
| 223 |
|
| 224 |
+
// هندلرهای درگ با پشتیبانی بهتر از موبایل
|
| 225 |
const handleL = document.getElementById('handleLeft');
|
| 226 |
const handleR = document.getElementById('handleRight');
|
| 227 |
|
| 228 |
+
// تابع کمکی برای شروع درگ
|
| 229 |
+
const startDrag = (e, type) => {
|
| 230 |
+
// جلوگیری از اسکرول صفحه هنگام لمس هندل
|
| 231 |
+
if(e.cancelable) e.preventDefault();
|
| 232 |
+
e.stopPropagation();
|
| 233 |
+
|
| 234 |
+
activeDragHandle = type;
|
| 235 |
+
|
| 236 |
+
// اضافه کردن لیسنرها به کل داکیومنت برای اینکه اگر انگشت از روی هندل رد شد، درگ قطع نشود
|
| 237 |
+
document.addEventListener('mousemove', onDrag);
|
| 238 |
+
document.addEventListener('mouseup', endDrag);
|
| 239 |
+
document.addEventListener('touchmove', onDrag, {passive: false});
|
| 240 |
+
document.addEventListener('touchend', endDrag);
|
| 241 |
};
|
| 242 |
|
| 243 |
+
// اتصال رویدادها
|
| 244 |
+
if(handleL) {
|
| 245 |
+
handleL.onmousedown = (e) => startDrag(e, 'left');
|
| 246 |
+
handleL.ontouchstart = (e) => startDrag(e, 'left');
|
| 247 |
+
}
|
| 248 |
+
if(handleR) {
|
| 249 |
+
handleR.onmousedown = (e) => startDrag(e, 'right');
|
| 250 |
+
handleR.ontouchstart = (e) => startDrag(e, 'right');
|
| 251 |
+
}
|
| 252 |
}
|
| 253 |
|
| 254 |
function onDrag(e) {
|
| 255 |
if (!activeDragHandle) return;
|
| 256 |
+
|
| 257 |
+
// جلوگیری از رفتارهای پیشفرض مرورگر (مثل رفرش یا اسکرول)
|
| 258 |
+
if(e.cancelable) e.preventDefault();
|
| 259 |
e.stopPropagation();
|
| 260 |
|
| 261 |
const strip = document.getElementById('timelineStrip');
|
| 262 |
if (!strip) return;
|
| 263 |
|
| 264 |
const rect = strip.getBoundingClientRect();
|
|
|
|
| 265 |
|
| 266 |
+
// دریافت موقعیت X چه با موس چه با تاچ
|
| 267 |
+
let clientX;
|
| 268 |
+
if (e.touches && e.touches.length > 0) {
|
| 269 |
+
clientX = e.touches[0].clientX;
|
| 270 |
+
} else {
|
| 271 |
+
clientX = e.clientX;
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
// محاسبه درصد دقیق مکان انگشت روی نوار
|
| 275 |
let percent = (clientX - rect.left) / rect.width;
|
| 276 |
+
|
| 277 |
+
// محدود کردن درصد بین 0 و 1 (که از کادر بیرون نزند)
|
| 278 |
percent = Math.max(0, Math.min(1, percent));
|
| 279 |
|
| 280 |
// تبدیل درصد به زمان واقعی
|
| 281 |
let newTime = trimMinLimit + (percent * trimViewDuration);
|
| 282 |
|
| 283 |
+
// گرد کردن برای دقت 2 رقم اعشار
|
| 284 |
+
newTime = Math.round(newTime * 100) / 100;
|
| 285 |
+
|
| 286 |
if (activeDragHandle === 'left') {
|
| 287 |
+
// محدودیت هندل چپ:
|
| 288 |
+
// 1. نباید از پایان جلو بزند (حداقل فاصله 0.05)
|
| 289 |
+
// 2. نباید از حد مجاز (کلمه قبل) کمتر شود
|
| 290 |
if (newTime >= tempEndTime - 0.05) newTime = tempEndTime - 0.05;
|
| 291 |
+
if (newTime < trimMinLimit) newTime = trimMinLimit;
|
| 292 |
|
| 293 |
+
tempStartTime = newTime;
|
| 294 |
} else {
|
| 295 |
+
// مح��ودیت هندل راست:
|
| 296 |
+
// 1. نباید از شروع عقب بیاید
|
| 297 |
+
// 2. نباید از حد مجاز (کلمه بعد) بیشتر شود
|
| 298 |
if (newTime <= tempStartTime + 0.05) newTime = tempStartTime + 0.05;
|
| 299 |
+
if (newTime > trimMaxLimit) newTime = trimMaxLimit;
|
| 300 |
|
| 301 |
+
tempEndTime = newTime;
|
| 302 |
}
|
| 303 |
|
| 304 |
+
// بروزرسانی آنی گرافیک
|
| 305 |
updateTrimmerVisuals();
|
| 306 |
}
|
| 307 |
|
| 308 |
+
function endDrag(e) {
|
| 309 |
+
if(!activeDragHandle) return;
|
| 310 |
+
if(e && e.cancelable) e.preventDefault();
|
| 311 |
+
|
| 312 |
activeDragHandle = null;
|
| 313 |
document.removeEventListener('mousemove', onDrag);
|
| 314 |
document.removeEventListener('mouseup', endDrag);
|
|
|
|
| 318 |
|
| 319 |
function updateTrimmerVisuals() {
|
| 320 |
// نمایش متنی زمان
|
| 321 |
+
const tStartDisp = document.getElementById('trimStartDisp');
|
| 322 |
+
const tEndDisp = document.getElementById('trimEndDisp');
|
| 323 |
+
|
| 324 |
+
if(tStartDisp) tStartDisp.innerText = formatTimeMs(tempStartTime);
|
| 325 |
+
if(tEndDisp) tEndDisp.innerText = formatTimeMs(tempEndTime);
|
| 326 |
|
| 327 |
+
// محاسبه درصدها برای CSS
|
| 328 |
+
let leftP = ((tempStartTime - trimMinLimit) / trimViewDuration) * 100;
|
| 329 |
+
let rightP = ((tempEndTime - trimMinLimit) / trimViewDuration) * 100;
|
| 330 |
+
|
| 331 |
+
// اطمینان از اینکه درصدها بین 0 تا 100 هستند
|
| 332 |
+
leftP = Math.max(0, Math.min(100, leftP));
|
| 333 |
+
rightP = Math.max(0, Math.min(100, rightP));
|
| 334 |
|
| 335 |
const handleL = document.getElementById('handleLeft');
|
| 336 |
const handleR = document.getElementById('handleRight');
|
| 337 |
const track = document.getElementById('trackActive');
|
| 338 |
|
| 339 |
+
// اعمال استایلها
|
| 340 |
+
if(handleL) handleL.style.left = `${leftP}%`;
|
| 341 |
+
if(handleR) handleR.style.left = `${rightP}%`;
|
| 342 |
+
|
| 343 |
+
if(track) {
|
|
|
|
| 344 |
track.style.left = `${leftP}%`;
|
| 345 |
track.style.width = `${rightP - leftP}%`;
|
| 346 |
}
|
|
|
|
| 355 |
|
| 356 |
// تابع پخش دقیق بازه انتخاب شده
|
| 357 |
function playPreviewWord() {
|
| 358 |
+
if ((tempStartTime === undefined) || (tempEndTime === undefined)) return;
|
| 359 |
|
|
|
|
| 360 |
if(previewInterval) clearInterval(previewInterval);
|
| 361 |
v.pause();
|
| 362 |
|
| 363 |
+
// پرش به لحظه شروع تنظیم شده
|
| 364 |
v.currentTime = tempStartTime;
|
| 365 |
|
| 366 |
const icon1 = document.getElementById('btnPreviewPlay')?.querySelector('i');
|
|
|
|
| 371 |
|
| 372 |
v.play().then(() => {
|
| 373 |
previewInterval = setInterval(() => {
|
| 374 |
+
// اگر زمان فعلی از زمان پایان رد شد، متوقف کن
|
| 375 |
if(v.currentTime >= tempEndTime) {
|
| 376 |
v.pause();
|
| 377 |
clearInterval(previewInterval);
|
|
|
|
| 379 |
if(icon1) icon1.className = "fa-solid fa-play";
|
| 380 |
if(icon2) icon2.className = "fa-solid fa-play";
|
| 381 |
|
| 382 |
+
// برگرداندن به زمان پایان برای نمایش فریم آخر
|
| 383 |
+
v.currentTime = tempEndTime;
|
| 384 |
}
|
| 385 |
}, 15);
|
| 386 |
}).catch(e => console.log("Play interrupted", e));
|
|
|
|
| 419 |
const [sIdx, wIdx] = activeWordId.split('-').map(Number);
|
| 420 |
const seg = state.segs[sIdx];
|
| 421 |
|
| 422 |
+
// اعمال تغییرات
|
| 423 |
seg.words[wIdx].start = tempStartTime;
|
| 424 |
seg.words[wIdx].end = tempEndTime;
|
| 425 |
|
|
|
|
| 426 |
if (wIdx === 0 && tempStartTime < seg.start) seg.start = tempStartTime;
|
| 427 |
if (wIdx === seg.words.length - 1 && tempEndTime > seg.end) seg.end = tempEndTime;
|
| 428 |
|