From 7eaed9d282df3420aac6d079d464f44890d2f5cf Mon Sep 17 00:00:00 2001 From: "Jehyeok Yeon (Tommy)" Date: Wed, 9 Oct 2024 22:04:24 -0500 Subject: [PATCH 01/12] added frontend functionality for editable timestamps --- .../Transcriptions/CaptionLine/index.js | 133 +++++++++++------- src/screens/Watch/model/trans_effects.js | 26 ++-- src/utils/cthttp/entities/Captions.js | 3 +- 3 files changed, 103 insertions(+), 59 deletions(-) diff --git a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js index 469a049bd..78e45ea66 100644 --- a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js +++ b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import { isMobile } from 'react-device-detect'; import * as KeyCode from 'keycode-js'; @@ -6,27 +6,58 @@ import { transControl, timeStrToSec, prettierTimeStr, - // WEBVTT_DESCRIPTIONS, } from '../../../Utils'; import './index.scss'; -function CaptionLine({ /* isCurrent = false, isEditing = false, - shouldHide = false, */ caption = {}, allowEdit, dispatch, fontSize }) { - let { text, id, /* startTime, */ begin, kind = "web" } = caption; - const ref = useRef(); +function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { + let { text, id, begin, kind = "web" } = caption; + const textRef = useRef(); + const timeRef = useRef(); + const [timeString, setTimeString] = useState(prettierTimeStr(begin)); - const blurFromInput = () => { + const convertTime = (input) => { + if (typeof input === 'number') { + return input; + } + const parts = input.split(':'); + if (parts.length === 3) { // HH:MM:SS + return parseInt(parts[0], 10) * 3600 + parseInt(parts[1], 10) * 60 + parseInt(parts[2], 10); + } + if (parts.length === 2) { // MM:SS + return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10); + } + if (parts.length === 1) { // SS + return parseInt(parts[0], 10); + } + throw new Error('Invalid time format'); + }; + + + const prettyTime = (seconds) => { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = seconds % 60; + if (hours > 0) { + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0') }`; + }; + + const blurFromInput = (ref) => { if (ref && ref.current && typeof ref.current.blur === 'function') { if (document.activeElement.id === ref.current.id) { - ref.current.innerText = text; ref.current.blur(); } } }; const handleSeek = () => { - const time = timeStrToSec(begin); - dispatch({ type: 'watch/media_setCurrTime', payload: time }) + try { + const time = convertTime(timeString); + dispatch({ type: 'watch/media_setCurrTime', payload: time }); + } catch (error) { + console.error('Error in handleSeek:', error); + } }; const handleChange = () => { @@ -34,70 +65,76 @@ function CaptionLine({ /* isCurrent = false, isEditing = false, }; const handleFocus = ({ target }) => { - // console.error(e.target.innerText) dispatch({ type: 'watch/setTransEditMode', payload: { caption, innerText: target.innerText } }) }; - const handleBlur = () => { - ref.current.innerText = text; + const handleBlur = (ref, originalValue) => { + ref.current.innerText = originalValue; transControl.handleBlur(); }; const handleSave = () => { - dispatch({ type: 'watch/saveCaption', payload: { caption, text: ref.current.innerText } }) + const newText = textRef.current.innerText; + try { + const newBegin = convertTime(timeRef.current.innerText); + // eslint-disable-next-line no-console + console.log(newBegin.toString()); // to see if local changes are being recognized and dispatched + dispatch({ type: 'watch/saveCaption', payload: { caption, text: newText, begin: newBegin } }); + } catch (error) { + console.error('Invalid time format'); + timeRef.current.innerText = timeString; + } }; const handleCancel = () => { - ref.current.innerText = text; + // eslint-disable-next-line no-console + console.log("canceling operations"); + textRef.current.innerText = text; + timeRef.current.innerText = timeString; dispatch({ type: 'watch/setCurrEditing', payload: null }) }; - const handleKeyDown = (e) => { - if (e.keyCode === KeyCode.KEY_RETURN && ! e.shiftKey) { + const handleKeyDown = (e, ref) => { + if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { e.preventDefault(); handleSave(); - blurFromInput(); + blurFromInput(ref); } }; - const timeStr = prettierTimeStr(String(begin)); - const hasUnsavedChanges = ref && ref.current && ref.current.innerText !== text; - let roundedTime = Math.round(begin); - let beginTime= Math.floor(roundedTime / 60) - let secondsTime = roundedTime % 60 - let secondsTimeString = String(secondsTime); - - - if (secondsTime < 10) { - secondsTimeString = `0${ String(secondsTime)}` - } - let totalTime = `${String(beginTime) }:${ secondsTimeString}`; - if (begin !== undefined) { - totalTime = prettierTimeStr(begin) - } + const hasUnsavedChanges = (textRef.current && textRef.current.innerText !== text) || + (timeRef.current && timeRef.current.innerText !== timeString); return (
- {/* Time Seeking Button */} - + {timeString} +
handleBlur(textRef, text)} onInput={handleChange} - onKeyDown={handleKeyDown} + onKeyDown={(e) => handleKeyDown(e, textRef)} spellCheck={false} - >{text} + > + {text}
- {/* Action Buttons */}
diff --git a/src/screens/Watch/model/trans_effects.js b/src/screens/Watch/model/trans_effects.js index e1e161823..63350abcc 100644 --- a/src/screens/Watch/model/trans_effects.js +++ b/src/screens/Watch/model/trans_effects.js @@ -215,35 +215,41 @@ export default { } }, // This is a transcript caption - *saveCaption({ payload: { caption, text } }, { call, put, select }) { + *saveCaption({ payload: { caption, text, begin } }, { call, put, select }) { const { watch } = yield select(); /** * @todo check PROFANITY_LIST */ + /** + * @todo check begin overlap or other timestamp checks + */ // currEditing could be missing if captions are frozen - if (!text || ! watch?.currEditing || (watch.currEditing && watch.currEditing.text === text)) { + if (!text || !watch?.currEditing || (watch.currEditing && watch.currEditing.text === text && watch.currEditing.begin === begin)) { promptControl.closePrompt(); return; // return this.edit(null); NOT IMPLEMENTED } + caption.text = text; // update data model - promptControl.savingCaption(); - + caption.begin = begin; + promptControl.savingCaption(); // just a ui prompt, empty atm + const { id } = watch.currEditing; // send user event uEvent.edittrans(watch.currTime, watch.currEditing.text, text); - // update new text - // this.currEditing_.text = text; ? - const isClosedCaption = caption.transcription.transcriptionType === 0; - + + const isClosedCaption = caption.transcription.transcriptionType === 0; + // will prob need to make this more flexible with chapter breaks + yield put({ type: 'setCurrEditing', payload: null }); try { - yield call(api.updateCaptionLine, { id, text }); + yield call(api.updateCaptionLine, { id, text, begin}); if(isClosedCaption) { yield put({ type: 'setCaptions', payload: watch.captions }); } else { yield put({ type: 'setDescriptions', payload: watch.descriptions }); - } + } + // another elif here for chapter breaks eventually promptControl.savedCaption(isClosedCaption, true); } catch (error) { promptControl.savedCaption(isClosedCaption, false); diff --git a/src/utils/cthttp/entities/Captions.js b/src/utils/cthttp/entities/Captions.js index 356074ada..c1bf2d626 100644 --- a/src/utils/cthttp/entities/Captions.js +++ b/src/utils/cthttp/entities/Captions.js @@ -29,7 +29,8 @@ export function searchCaptionInOffering(offeringId, query, filterLanguage = 'en- // POST export function updateCaptionLine(data) { - return cthttp.post('Captions', { id: data.id, text: data.text }); + return cthttp.post('Captions', { id: data.id, text: data.text, data: data.begin }); + // added begin, but likely needs backend change to receive timestamp data } export function searchCaptions(transList, data) { From c5a1f5d9cde97169f5324d41a8a2653f4b213616 Mon Sep 17 00:00:00 2001 From: Tommy Yeon Date: Wed, 23 Oct 2024 18:11:46 -0500 Subject: [PATCH 02/12] added end timestamp and updating functionality --- .../Transcriptions/CaptionLine/index.js | 122 ++++++++++++------ src/screens/Watch/Utils/helpers.js | 19 ++- src/screens/Watch/model/trans_effects.js | 50 +++++-- src/utils/cthttp/entities/Captions.js | 23 +++- 4 files changed, 156 insertions(+), 58 deletions(-) diff --git a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js index 78e45ea66..926d45c04 100644 --- a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js +++ b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js @@ -1,97 +1,117 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { isMobile } from 'react-device-detect'; import * as KeyCode from 'keycode-js'; import { transControl, - timeStrToSec, prettierTimeStr, } from '../../../Utils'; import './index.scss'; function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { - let { text, id, begin, kind = "web" } = caption; + let { text, id, begin, end, kind = "web" } = caption; const textRef = useRef(); const timeRef = useRef(); + const endTimeRef = useRef(); const [timeString, setTimeString] = useState(prettierTimeStr(begin)); + const [endTimeString, setEndTimeString] = useState(prettierTimeStr(end)); // Initialize with correct end time - const convertTime = (input) => { - if (typeof input === 'number') { - return input; - } + const validateTimeFormat = (input) => { + console.log(`Validating time format: ${input}`); // Debugging line const parts = input.split(':'); - if (parts.length === 3) { // HH:MM:SS - return parseInt(parts[0], 10) * 3600 + parseInt(parts[1], 10) * 60 + parseInt(parts[2], 10); - } - if (parts.length === 2) { // MM:SS - return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10); - } - if (parts.length === 1) { // SS - return parseInt(parts[0], 10); + if (parts.length === 3 || parts.length === 2 || parts.length === 1) { + return true; } throw new Error('Invalid time format'); }; - - const prettyTime = (seconds) => { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - const secs = seconds % 60; - if (hours > 0) { - return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + + const convertTimeToSeconds = (time) => { + const parts = time.split(':').map(Number); + let totalSeconds = 0; + + // Assuming the format is hh:mm:ss or mm:ss + if (parts.length === 3) { + totalSeconds += parts[0] * 3600; // hours + totalSeconds += parts[1] * 60; // minutes + totalSeconds += parts[2]; // seconds + } else if (parts.length === 2) { + totalSeconds += parts[0] * 60; // minutes + totalSeconds += parts[1]; // seconds + } else if (parts.length === 1) { + totalSeconds += parts[0]; // seconds + } else { + throw new Error('Invalid time format'); } - return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0') }`; + + return totalSeconds; }; + const blurFromInput = (ref) => { if (ref && ref.current && typeof ref.current.blur === 'function') { if (document.activeElement.id === ref.current.id) { ref.current.blur(); + console.log(`Blurred input: ${ref.current.id}`); // Debugging line } } }; const handleSeek = () => { try { - const time = convertTime(timeString); - dispatch({ type: 'watch/media_setCurrTime', payload: time }); + validateTimeFormat(timeString); + const timeInSeconds = convertTimeToSeconds(timeString); // Convert time to seconds + console.log(`Seeking to time: ${timeInSeconds}`); // Debugging line + dispatch({ type: 'watch/media_setCurrTime', payload: timeInSeconds }); } catch (error) { console.error('Error in handleSeek:', error); } }; - const handleChange = () => { - // console.log(target.innerText) + const handleChange = (ref) => { + console.log(`Input changed: ${ref.current.innerText}`); // Debugging line }; const handleFocus = ({ target }) => { - dispatch({ type: 'watch/setTransEditMode', payload: { caption, innerText: target.innerText } }) + dispatch({ type: 'watch/setTransEditMode', payload: { caption, innerText: target.innerText } }); + console.log(`Focused on: ${target.id}`); // Debugging line }; const handleBlur = (ref, originalValue) => { - ref.current.innerText = originalValue; - transControl.handleBlur(); + if (ref.current) { + setTimeString(originalValue); + transControl.handleBlur(); + } + console.log(`Blurred ${ref.current.id}, restored value: ${originalValue}`); // Debugging line }; const handleSave = () => { const newText = textRef.current.innerText; try { - const newBegin = convertTime(timeRef.current.innerText); - // eslint-disable-next-line no-console - console.log(newBegin.toString()); // to see if local changes are being recognized and dispatched - dispatch({ type: 'watch/saveCaption', payload: { caption, text: newText, begin: newBegin } }); + console.log(`begin: ${begin} end: ${end}`); // Debugging line + validateTimeFormat(timeRef.current.innerText); + validateTimeFormat(endTimeRef.current.innerText); + console.log(`handleSave: before editing: ${newText} at time: ${timeString} with end time: ${endTimeString}`); // Debugging line + dispatch({ type: 'watch/saveCaption', payload: { caption, text: newText, begin: timeRef.current.innerText, end: endTimeRef.current.innerText } }); + setTimeString(timeRef.current.innerText); + setEndTimeString(endTimeRef.current.innerText); + textRef.current.innerText = newText; + console.log(`handleSave: after editing: ${newText} at time: ${timeString} with end time: ${endTimeString}`); // Debugging line + } catch (error) { console.error('Invalid time format'); + alert('Please enter a valid time format (HH:MM:SS or MM:SS or SS)'); timeRef.current.innerText = timeString; + endTimeRef.current.innerText = endTimeString; } }; const handleCancel = () => { - // eslint-disable-next-line no-console - console.log("canceling operations"); + console.log("Canceling operations"); // Debugging line textRef.current.innerText = text; timeRef.current.innerText = timeString; - dispatch({ type: 'watch/setCurrEditing', payload: null }) + endTimeRef.current.innerText = endTimeString; // Restore original end time + dispatch({ type: 'watch/setCurrEditing', payload: null }); }; const handleKeyDown = (e, ref) => { @@ -103,7 +123,8 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { }; const hasUnsavedChanges = (textRef.current && textRef.current.innerText !== text) || - (timeRef.current && timeRef.current.innerText !== timeString); + (timeRef.current && timeRef.current.innerText !== timeString) || + (endTimeRef.current && endTimeRef.current.innerText !== endTimeString); return (
handleBlur(timeRef, timeString)} - onInput={handleChange} + onInput={() => handleChange(timeRef)} onKeyDown={(e) => handleKeyDown(e, timeRef)} onClick={handleSeek} aria-label={`Edit time: ${timeString}`} @@ -143,12 +164,31 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { className={`caption-line-text-${fontSize}`} onFocus={handleFocus} onBlur={() => handleBlur(textRef, text)} - onInput={handleChange} + onInput={() => handleChange(textRef)} onKeyDown={(e) => handleKeyDown(e, textRef)} spellCheck={false} > {text}
+ + {/* Editable End Time */} +
handleBlur(endTimeRef, endTimeString)} + onInput={() => handleChange(endTimeRef)} + onKeyDown={(e) => handleKeyDown(e, endTimeRef)} + aria-label={`Edit end time: ${endTimeString}`} + spellCheck={false} + > + {endTimeString} +
{/* Action Buttons */} @@ -167,7 +207,7 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { Save - diff --git a/src/screens/Watch/Utils/helpers.js b/src/screens/Watch/Utils/helpers.js index 55a9a16d2..362cc0c56 100644 --- a/src/screens/Watch/Utils/helpers.js +++ b/src/screens/Watch/Utils/helpers.js @@ -43,25 +43,58 @@ export function isLater(time) { return ({ begin }) => time <= timeStrToSec(begin); } -export function prettierTimeStr(str) { - if (typeof str !== 'string') return ''; - - const strs = str.split(':'); - if (strs.length !== 3) return ''; // Ensure the input is in HH:MM:SS format +export function prettierTimeStr(time, showMilliseconds = false) { + if (typeof time !== 'string') return ''; - let hours = parseInt(strs[0], 10); - let mins = parseInt(strs[1], 10); - let sec = parseInt(strs[2], 10); + const parts = time.split(':').map((part) => parseFloat(part)); + let hours = 0, + mins = 0, + secs = 0, + millis = 0; - // Format minutes and seconds to two digits - if (hours < 10) hours = `0${hours}`; - if (mins < 10) mins = `0${mins}`; - if (sec < 10) sec = `0${sec}`; + if (parts.length === 3) { + hours = parts[0]; + mins = parts[1]; + secs = Math.floor(parts[2]); + millis = Math.round((parts[2] % 1) * 1000); + } else if (parts.length === 2) { + mins = parts[0]; + secs = Math.floor(parts[1]); + millis = Math.round((parts[1] % 1) * 1000); + } else if (parts.length === 1) { + secs = Math.floor(parts[0]); + millis = Math.round((parts[0] % 1) * 1000); + } - return `${hours}:${mins}:${sec}`; + const format = (num, digits = 2) => String(num).padStart(digits, '0'); + const formattedTime = `${format(hours)}:${format(mins)}:${format(secs)}`; + + return showMilliseconds + ? `${formattedTime}.${format(millis, 3)}` + : formattedTime; } + +// export function prettierTimeStr(str) { +// if (typeof str !== 'string') return ''; + +// const strs = str.split(':'); +// if (strs.length !== 3) return ''; // Ensure the input is in HH:MM:SS format + +// let hours = parseInt(strs[0], 10); +// let mins = parseInt(strs[1], 10); +// let sec = parseInt(strs[2], 10); + +// // Format minutes and seconds to two digits +// if (hours < 10) hours = `0${hours}`; +// if (mins < 10) mins = `0${mins}`; +// if (sec < 10) sec = `0${sec}`; + +// return `${hours}:${mins}:${sec}`; +// } + + export function getCCSelectOptions(array = [], operation = (item) => item) { const options = []; array.forEach((item) => { diff --git a/src/utils/cthttp/entities/Captions.js b/src/utils/cthttp/entities/Captions.js index ab3505865..6e4aab1b1 100644 --- a/src/utils/cthttp/entities/Captions.js +++ b/src/utils/cthttp/entities/Captions.js @@ -29,10 +29,12 @@ export function searchCaptionInOffering(offeringId, query, filterLanguage = 'en- // POST export function updateCaptionLine(data) { + // eslint-disable-next-line no-console console.log("Preparing to update caption line with data:", data); // Check if all required fields are present if (!data.id || !data.text || !data.begin || !data.end) { + // eslint-disable-next-line no-console console.error("Missing required data fields:", data); throw new Error("Required data fields are missing."); } @@ -43,9 +45,11 @@ export function updateCaptionLine(data) { begin: data.begin, end: data.end }).then(response => { + // eslint-disable-next-line no-console console.log("Caption line updated successfully:", response); return response; }).catch(error => { + // eslint-disable-next-line no-console console.error("Error updating caption line:", error); throw error; // Re-throw to handle it upstream if necessary }); From 990ba5b2c41344082d02b93a3954f1034bb7ab93 Mon Sep 17 00:00:00 2001 From: "Jehyeok Yeon (Tommy)" Date: Wed, 20 Nov 2024 09:19:50 -0600 Subject: [PATCH 06/12] fixing lint errors --- .../Transcriptions/CaptionLine/index.js | 16 ++++++++++++++++ src/screens/Watch/Utils/helpers.js | 8 ++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js index c37ce2ab7..11f3caea4 100644 --- a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js +++ b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js @@ -18,10 +18,12 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { const [endTimeString, setEndTimeString] = useState(prettierTimeStr(end)); useEffect(() => { + // eslint-disable-next-line no-console console.log("Full Begin Time Updated:", fullBeginTime); }, [fullBeginTime]); useEffect(() => { + // eslint-disable-next-line no-console console.log("Full End Time Updated:", fullEndTime); }, [fullEndTime]); @@ -44,8 +46,11 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { const newText = textRef.current.innerText; try { + // eslint-disable-next-line no-console console.log("Handle save triggered"); + // eslint-disable-next-line no-console console.log("Using begin time:", updatedBeginTime); + // eslint-disable-next-line no-console console.log("Using end time:", updatedEndTime); validateTimeFormat(updatedBeginTime); @@ -58,9 +63,11 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { setTimeString(prettierTimeStr(updatedBeginTime)); setEndTimeString(prettierTimeStr(updatedEndTime)); + // eslint-disable-next-line no-console console.log("Time strings updated to:", prettierTimeStr(updatedBeginTime), prettierTimeStr(updatedEndTime)); textRef.current.innerText = newText; } catch (error) { + // eslint-disable-next-line no-console console.error("Error during save:", error.message); alert('Please enter a valid time format (HH:MM:SS, MM:SS, or HH:MM:SS.SSS)'); } @@ -76,16 +83,19 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { try { validateTimeFormat(currentValue); setFullTime((prev) => { + // eslint-disable-next-line no-console console.log("Updating time:", currentValue); return currentValue; }); ref.current.innerText = prettierTimeStr(currentValue); } catch { + // eslint-disable-next-line no-console console.warn("Invalid time format. Reverting to original value:", originalValue); ref.current.innerText = prettierTimeStr(originalValue); } } else if (elementId.includes("textarea")) { // text + // eslint-disable-next-line no-console console.log("handling blur for text"); ref.current.innerText = currentValue.trim(); } @@ -106,12 +116,15 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { }; const handleKeyDown = (e, ref, setFullTime) => { + // eslint-disable-next-line no-console console.log("Key down event:", e.keyCode); if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { e.preventDefault(); const elementId = ref.current?.id || ""; const currentTime = ref.current?.innerText || ""; + // eslint-disable-next-line no-console console.log("Element ID:", elementId); + // eslint-disable-next-line no-console console.log("Current time value from input:", currentTime); if (elementId.includes("time")) { @@ -119,6 +132,7 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { try { validateTimeFormat(currentTime); setFullTime(currentTime); + // eslint-disable-next-line no-console console.log("Updated full time (pending state):", currentTime); if (elementId.includes("end")) { handleSave(fullBeginTime, currentTime); @@ -127,11 +141,13 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { } } catch (error) { + // eslint-disable-next-line no-console console.error("Invalid time format during keydown:", error.message); alert('Please enter a valid time format (HH:MM:SS, MM:SS, or HH:MM:SS.SSS)'); } } else if (elementId.includes("textarea")) { // text + // eslint-disable-next-line no-console console.log("saving text"); handleSave(); } diff --git a/src/screens/Watch/Utils/helpers.js b/src/screens/Watch/Utils/helpers.js index 362cc0c56..b64cfde7f 100644 --- a/src/screens/Watch/Utils/helpers.js +++ b/src/screens/Watch/Utils/helpers.js @@ -47,10 +47,10 @@ export function prettierTimeStr(time, showMilliseconds = false) { if (typeof time !== 'string') return ''; const parts = time.split(':').map((part) => parseFloat(part)); - let hours = 0, - mins = 0, - secs = 0, - millis = 0; + let hours = 0; + let mins = 0; + let secs = 0; + let millis = 0; if (parts.length === 3) { hours = parts[0]; From 2039e97957ec3c4c24e1132111d29018781b7a32 Mon Sep 17 00:00:00 2001 From: "Jehyeok Yeon (Tommy)" Date: Wed, 20 Nov 2024 09:43:43 -0600 Subject: [PATCH 07/12] fixing lint errors --- .../Components/Transcriptions/CaptionLine/index.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js index 11f3caea4..2d15dddbd 100644 --- a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js +++ b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js @@ -55,12 +55,10 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { validateTimeFormat(updatedBeginTime); validateTimeFormat(updatedEndTime); - dispatch({ type: 'watch/saveCaption', payload: { caption, text: newText, begin: updatedBeginTime, end: updatedEndTime }, }); - setTimeString(prettierTimeStr(updatedBeginTime)); setEndTimeString(prettierTimeStr(updatedEndTime)); // eslint-disable-next-line no-console @@ -69,7 +67,7 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { } catch (error) { // eslint-disable-next-line no-console console.error("Error during save:", error.message); - alert('Please enter a valid time format (HH:MM:SS, MM:SS, or HH:MM:SS.SSS)'); + alert('Please enter a valid time format (HH:MM:SS, MM:SS, or HH:MM:SS.SSS)'); // eslint-disable-line no-alert } }; @@ -77,7 +75,6 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { if (ref.current) { const elementId = ref.current?.id || ""; const currentValue = ref.current.innerText; - if (elementId.includes("time")) { // time try { @@ -85,6 +82,8 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { setFullTime((prev) => { // eslint-disable-next-line no-console console.log("Updating time:", currentValue); + // eslint-disable-next-line no-console + console.log(`${prev}`); return currentValue; }); ref.current.innerText = prettierTimeStr(currentValue); @@ -126,7 +125,6 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { console.log("Element ID:", elementId); // eslint-disable-next-line no-console console.log("Current time value from input:", currentTime); - if (elementId.includes("time")) { // time try { @@ -139,11 +137,10 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { } else { handleSave(currentTime, fullEndTime); } - } catch (error) { // eslint-disable-next-line no-console console.error("Invalid time format during keydown:", error.message); - alert('Please enter a valid time format (HH:MM:SS, MM:SS, or HH:MM:SS.SSS)'); + alert('Please enter a valid time format (HH:MM:SS, MM:SS, or HH:MM:SS.SSS)'); // eslint-disable-line no-alert } } else if (elementId.includes("textarea")) { // text From 1677b69eeea34a4d1b559a53f0249eb30beb4599 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Tue, 3 Dec 2024 14:42:47 -0600 Subject: [PATCH 08/12] refactor CaptionLine to remove logging and split text/time functionality --- .../Transcriptions/CaptionLine/index.js | 155 +++++------------- 1 file changed, 43 insertions(+), 112 deletions(-) diff --git a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js index 2d15dddbd..c5706d26e 100644 --- a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js +++ b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js @@ -2,7 +2,7 @@ import React, { useRef, useState, useEffect } from 'react'; import { isMobile } from 'react-device-detect'; import * as KeyCode from 'keycode-js'; -import { transControl, prettierTimeStr } from '../../../Utils'; +import { prettierTimeStr } from '../../../Utils'; import './index.scss'; function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { @@ -17,16 +17,6 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { const [timeString, setTimeString] = useState(prettierTimeStr(begin)); const [endTimeString, setEndTimeString] = useState(prettierTimeStr(end)); - useEffect(() => { - // eslint-disable-next-line no-console - console.log("Full Begin Time Updated:", fullBeginTime); - }, [fullBeginTime]); - - useEffect(() => { - // eslint-disable-next-line no-console - console.log("Full End Time Updated:", fullEndTime); - }, [fullEndTime]); - const validateTimeFormat = (input) => { const timeRegex = /^(\d{1,2}:)?\d{1,2}:\d{2}(\.\d+)?$/; if (!timeRegex.test(input)) { @@ -35,74 +25,38 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { return true; }; - const handleCancel = () => { - textRef.current.innerText = text; - timeRef.current.innerText = prettierTimeStr(fullBeginTime); - endTimeRef.current.innerText = prettierTimeStr(fullEndTime); - dispatch({ type: 'watch/setCurrEditing', payload: null }); - }; - - const handleSave = (updatedBeginTime = fullBeginTime, updatedEndTime = fullEndTime) => { + const handleSave = () => { const newText = textRef.current.innerText; - + const newBeginTime = timeRef.current.innerText; + const newEndTime = endTimeRef.current.innerText; try { - // eslint-disable-next-line no-console - console.log("Handle save triggered"); - // eslint-disable-next-line no-console - console.log("Using begin time:", updatedBeginTime); - // eslint-disable-next-line no-console - console.log("Using end time:", updatedEndTime); - - validateTimeFormat(updatedBeginTime); - validateTimeFormat(updatedEndTime); + validateTimeFormat(newBeginTime); + validateTimeFormat(newEndTime); dispatch({ type: 'watch/saveCaption', - payload: { caption, text: newText, begin: updatedBeginTime, end: updatedEndTime }, + payload: { caption, text: newText, begin: newBeginTime, end: newEndTime }, }); - setTimeString(prettierTimeStr(updatedBeginTime)); - setEndTimeString(prettierTimeStr(updatedEndTime)); - // eslint-disable-next-line no-console - console.log("Time strings updated to:", prettierTimeStr(updatedBeginTime), prettierTimeStr(updatedEndTime)); + setTimeString(prettierTimeStr(newBeginTime)); + setEndTimeString(prettierTimeStr(newEndTime)); textRef.current.innerText = newText; } catch (error) { - // eslint-disable-next-line no-console - console.error("Error during save:", error.message); - alert('Please enter a valid time format (HH:MM:SS, MM:SS, or HH:MM:SS.SSS)'); // eslint-disable-line no-alert + // TODO: add reactful alert here if timestring is badly formatted } }; - const handleBlur = (ref, originalValue, setFullTime) => { + // NOTE: ALL editable text boxes reset the value to the original if the textbox loses focus + // Users MUST hit enter for their changes to not be lost + const handleTextBlur = (ref, originalValue) => { if (ref.current) { - const elementId = ref.current?.id || ""; - const currentValue = ref.current.innerText; - if (elementId.includes("time")) { - // time - try { - validateTimeFormat(currentValue); - setFullTime((prev) => { - // eslint-disable-next-line no-console - console.log("Updating time:", currentValue); - // eslint-disable-next-line no-console - console.log(`${prev}`); - return currentValue; - }); - ref.current.innerText = prettierTimeStr(currentValue); - } catch { - // eslint-disable-next-line no-console - console.warn("Invalid time format. Reverting to original value:", originalValue); - ref.current.innerText = prettierTimeStr(originalValue); - } - } else if (elementId.includes("textarea")) { - // text - // eslint-disable-next-line no-console - console.log("handling blur for text"); - ref.current.innerText = currentValue.trim(); - } + ref.current.innerText = originalValue; + } + }; + + const handleTimeBlur = (ref, originalValue) => { + if (ref.current) { + ref.current.innerText = prettierTimeStr(originalValue); } - - transControl.handleBlur(); }; - const handleFocus = (ref, fullTime) => { if (ref.current) { @@ -114,57 +68,34 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { } }; - const handleKeyDown = (e, ref, setFullTime) => { - // eslint-disable-next-line no-console - console.log("Key down event:", e.keyCode); + const handleTimeKeyDown = (e, ref, setFullTime) => { if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { e.preventDefault(); - const elementId = ref.current?.id || ""; const currentTime = ref.current?.innerText || ""; - // eslint-disable-next-line no-console - console.log("Element ID:", elementId); - // eslint-disable-next-line no-console - console.log("Current time value from input:", currentTime); - if (elementId.includes("time")) { - // time - try { - validateTimeFormat(currentTime); - setFullTime(currentTime); - // eslint-disable-next-line no-console - console.log("Updated full time (pending state):", currentTime); - if (elementId.includes("end")) { - handleSave(fullBeginTime, currentTime); - } else { - handleSave(currentTime, fullEndTime); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error("Invalid time format during keydown:", error.message); - alert('Please enter a valid time format (HH:MM:SS, MM:SS, or HH:MM:SS.SSS)'); // eslint-disable-line no-alert - } - } else if (elementId.includes("textarea")) { - // text - // eslint-disable-next-line no-console - console.log("saving text"); + try { + validateTimeFormat(currentTime); + setFullTime(currentTime); handleSave(); + } catch (error) { + // TODO: add reactful alert here if timestring is badly formatted } - ref.current.blur(); } - }; - + } - const hasUnsavedChanges = - (textRef.current && textRef.current.innerText !== text) || - (timeRef.current && fullBeginTime !== begin) || - (endTimeRef.current && fullEndTime !== end); + const handleTextKeyDown = (e, ref) => { + if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { + handleSave(); + ref.current.blur(); + } + } return (
{/* Editable Start Time */} @@ -177,8 +108,8 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { id={`caption-line-time-${id}`} className="caption-line-time-display" onFocus={() => handleFocus(timeRef, fullBeginTime)} - onBlur={() => handleBlur(timeRef, fullBeginTime, setFullBeginTime)} - onKeyDown={(e) => handleKeyDown(e, timeRef, setFullBeginTime)} + onBlur={() => handleTimeBlur(timeRef, fullBeginTime)} + onKeyDown={(e) => handleTimeKeyDown(e, timeRef, setFullBeginTime)} spellCheck={false} > {timeString} @@ -194,8 +125,8 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { id={`caption-line-textarea-${id}`} className={`caption-line-text-${fontSize}`} onFocus={() => dispatch({ type: 'watch/setTransEditMode', payload: { caption } })} - onBlur={() => handleBlur(textRef, text, () => {})} - onKeyDown={(e) => handleKeyDown(e, textRef, () => {})} + onBlur={() => handleTextBlur(textRef, text)} + onKeyDown={(e) => handleTextKeyDown(e, textRef)} spellCheck={false} > {text} @@ -211,8 +142,8 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { id={`caption-line-end-time-${id}`} className="caption-line-time-display" onFocus={() => handleFocus(endTimeRef, fullEndTime)} - onBlur={() => handleBlur(endTimeRef, fullEndTime, setFullEndTime)} - onKeyDown={(e) => handleKeyDown(e, endTimeRef, setFullEndTime)} + onBlur={() => handleTimeBlur(endTimeRef, fullEndTime)} + onKeyDown={(e) => handleTimeKeyDown(e, endTimeRef, setFullEndTime)} spellCheck={false} > {endTimeString} @@ -220,8 +151,8 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) {
{/* Action Buttons */} -
- {hasUnsavedChanges && ( + {/*
+ {true && (
Return (save changes). Shift-Return (newline)
)} -
+
*/}
); } From 6df28ed23525745d67b91ff2888ed518e73ec5cd Mon Sep 17 00:00:00 2001 From: dsding2 Date: Tue, 3 Dec 2024 14:56:15 -0600 Subject: [PATCH 09/12] comment out some logging, lint file --- src/screens/Watch/model/trans_effects.js | 104 +++++++++++------------ 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/screens/Watch/model/trans_effects.js b/src/screens/Watch/model/trans_effects.js index a223ea0dd..98bf37e58 100644 --- a/src/screens/Watch/model/trans_effects.js +++ b/src/screens/Watch/model/trans_effects.js @@ -62,24 +62,24 @@ export default { // We have an array of transcript Ids to display, time to get the actual transcripts from the server *setCurrTrans({ payload: trans }, { all, call, put }) { - console.log("Starting setCurrTrans with trans payload:", trans); - + // console.log("Starting setCurrTrans with trans payload:", trans); + // Ensure trans is an array - if (!Array.isArray(trans)) { - trans = [trans]; + if (!Array.isArray(trans)) { + trans = [trans]; } - console.log("Normalized trans array:", trans); - + // console.log("Normalized trans array:", trans); + let alldata; if (trans.length > 0) { console.log("Fetching captions for each transcription ID..."); - + // Fetch data for each transcription ID const allTranscriptionData = yield all( trans.map((tran) => call(api.getCaptionsByTranscriptionId, tran.id)) ); - console.log("Fetched allTranscriptionData:", allTranscriptionData); - + // console.log("Fetched allTranscriptionData:", allTranscriptionData); + // Attach transcription reference to each caption allTranscriptionData.forEach((captionList, listIndex) => { const t = trans[listIndex]; @@ -88,44 +88,44 @@ export default { console.log(`Assigned transcription for caption (ID: ${c.id}):`, c.transcription); }); }); - + // Merge all caption data into alldata alldata = allTranscriptionData.reduce((acc, { data = [] }) => [...acc, ...data], []); - console.log("Combined alldata:", alldata); + console.log("Combined alldata:", alldata); } - - if (alldata === undefined) { - alldata = []; + + if (alldata === undefined) { + alldata = []; } - + // Filter captions by transcription type let closedcaptions = alldata.filter((c) => c.transcription.transcriptionType === 0); let descriptions = alldata.filter((c) => c.transcription.transcriptionType !== 0); console.log("Filtered closedcaptions:", closedcaptions); - console.log("Filtered descriptions:", descriptions); - + console.log("Filtered descriptions:", descriptions); + // Dispatch closed captions yield put({ type: 'setCaptions', payload: closedcaptions }); - + // Dispatch descriptions const descriptionData = descriptions; console.log("Dispatching descriptionData:", descriptionData); yield put.resolve({ type: 'setDescriptions', payload: descriptionData }); - + // Dispatch final transcript set yield put({ type: 'setTranscript' }); console.log("Completed setCurrTrans"); }, - - *setTranscriptions({ payload: trans }, { put, select}) { + + *setTranscriptions({ payload: trans }, { put, select }) { const { playerpref } = yield select(); - let keys = playerpref.transKeys ; - if( keys === undefined ) { + let keys = playerpref.transKeys; + if (keys === undefined) { // No preference, so look for 1 caption and 1 description... const seen = new Set(); keys = []; for (const t of trans) { - if(! seen.has(t.transcriptionType)) { + if (!seen.has(t.transcriptionType)) { keys.push(t.transKey); seen.add(t.transcriptionType); } @@ -147,17 +147,17 @@ export default { const prevCaption_ = watch.caption; // TODO: Fix: if watch.transcript is ARRAY_EMPTY (?) or length 0 then findCurrent will // return with the current transcript. Should put({ type: 'setCurrCaption', payload: null - + // TODO: is index reset in frontend? the findCurrent assumes index are increasing integers // Ans: Yes in model.js/setTranscript, after filtering wanted transcriptions - // "transcript= _.map(transcript, (item, index) => ({ ...item, index })); - + const next = findCurrent(watch.transcript, prevCaption_, currentTime); if (next && next.id) { // console.log(next); // pause video if it's AD - + // determine whether should scroll smoothly const smoothScroll = prevCaption_ && next && Math.abs(prevCaption_.index - next.index) === 1; @@ -181,7 +181,7 @@ export default { } // Speak out loud console.log(`SPEAK ${nextDescription.text}`); - yield put({ type: 'playerpref/setPreference', payload: { description: nextDescription.text } }) + yield put({ type: 'playerpref/setPreference', payload: { description: nextDescription.text } }) } } return next || null; @@ -189,20 +189,20 @@ export default { }, *setLanguage({ payload: language }, { put, select }) { const { watch } = yield select(); - const currTrans = findTransByLanguages(watch.transcriptions,[language]); + const currTrans = findTransByLanguages(watch.transcriptions, [language]); if (currTrans) { yield put({ type: 'setCurrTrans', payload: currTrans }); } uEvent.langchange(watch.time, language); uEvent.registerLanguage(language); }, - *setCurrentTranscriptionMulti( _ignore, { put, select }) { + *setCurrentTranscriptionMulti(_ignore, { put, select }) { const { watch } = yield select(); - + const selected = watch.currentTranscriptionMulti.transKeysSelected - + const currTrans = watch.transcriptions.filter((t) => selected.includes(t.transKey)); - + // if (currTrans.length > 0) { yield put({ type: 'setCurrTrans', payload: currTrans }); // } @@ -217,10 +217,10 @@ export default { *setTransEditMode({ payload: { caption } }, { put, select }) { // if no param caption, edit current caption const { watch, playerpref } = yield select(); - + const media = watch.media; const crowdEdit = media.crowdEditMode !== CROWDEDIT_FREEZE_ALL; - if (! crowdEdit) { + if (!crowdEdit) { return; } @@ -237,17 +237,17 @@ export default { // This is a transcript caption *saveCaption({ payload: { caption, text, begin, end } }, { call, put, select }) { const { watch } = yield select(); - - console.log("Entering saveCaption with payload:", { caption, text, begin, end }); + + // console.log("Entering saveCaption with payload:", { caption, text, begin, end }); console.log("Current watch state:", watch); - + /** * @todo check PROFANITY_LIST */ /** * @todo check begin overlap or other timestamp checks */ - + // currEditing could be missing if captions are frozen // if (!text || !watch?.currEditing || (watch.currEditing && watch.currEditing.text === text && watch.currEditing.begin === begin)) { // console.log("Exiting saveCaption early. Conditions not met."); @@ -261,44 +261,44 @@ export default { promptControl.closePrompt(); return; } - + if (!watch?.currEditing) { console.log("Exiting saveCaption early: 'watch.currEditing' is falsy."); promptControl.closePrompt(); return; } - + if (watch.currEditing && watch.currEditing.text === text && watch.currEditing.begin === begin && watch.currEditing.end === end) { console.log("Exiting saveCaption early: No changes detected in 'currEditing'."); promptControl.closePrompt(); return; - } - + } + console.log("Updating caption with text:", text); caption.text = text; // update data model - caption.begin = begin; + caption.begin = begin; caption.end = end; promptControl.savingCaption(); // just a ui prompt, empty atm - + const { id } = watch.currEditing; console.log("Sending user event with ID:", id, "Current time:", watch.currTime, "Old text:", watch.currEditing.text, "New text:", text); - - const isClosedCaption = caption.transcription.transcriptionType === 0; + + const isClosedCaption = caption.transcription.transcriptionType === 0; console.log("Is closed caption:", isClosedCaption); - + yield put({ type: 'setCurrEditing', payload: null }); - + try { console.log("Calling API to update caption line with data:", { id, text, begin, end }); yield call(api.updateCaptionLine, { id, text, begin, end }); - + if (isClosedCaption) { console.log("Updating closed captions in state."); yield put({ type: 'setCaptions', payload: watch.captions }); } else { console.log("Updating descriptions in state."); yield put({ type: 'setDescriptions', payload: watch.descriptions }); - } + } // another elif here for chapter breaks eventually promptControl.savedCaption(isClosedCaption, true); console.log("Caption saved successfully."); @@ -306,7 +306,7 @@ export default { console.error("Error saving caption:", error); promptControl.savedCaption(isClosedCaption, false); } - }, + }, *setFontSize({ payload: fontSize }, { put, select }) { const { watch } = yield select(); if (fontSize == null) { From ae27cb5eb406fbcfda84f7a2415c7edf495ae616 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Tue, 3 Dec 2024 17:58:29 -0600 Subject: [PATCH 10/12] comment out almost all logging --- src/screens/Watch/model/trans_effects.js | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/screens/Watch/model/trans_effects.js b/src/screens/Watch/model/trans_effects.js index 98bf37e58..b23acedac 100644 --- a/src/screens/Watch/model/trans_effects.js +++ b/src/screens/Watch/model/trans_effects.js @@ -72,7 +72,7 @@ export default { let alldata; if (trans.length > 0) { - console.log("Fetching captions for each transcription ID..."); + // console.log("Fetching captions for each transcription ID..."); // Fetch data for each transcription ID const allTranscriptionData = yield all( @@ -85,13 +85,13 @@ export default { const t = trans[listIndex]; captionList.data?.forEach((c) => { c.transcription = t; - console.log(`Assigned transcription for caption (ID: ${c.id}):`, c.transcription); + // console.log(`Assigned transcription for caption (ID: ${c.id}):`, c.transcription); }); }); // Merge all caption data into alldata alldata = allTranscriptionData.reduce((acc, { data = [] }) => [...acc, ...data], []); - console.log("Combined alldata:", alldata); + // console.log("Combined alldata:", alldata); } if (alldata === undefined) { @@ -101,20 +101,20 @@ export default { // Filter captions by transcription type let closedcaptions = alldata.filter((c) => c.transcription.transcriptionType === 0); let descriptions = alldata.filter((c) => c.transcription.transcriptionType !== 0); - console.log("Filtered closedcaptions:", closedcaptions); - console.log("Filtered descriptions:", descriptions); + // console.log("Filtered closedcaptions:", closedcaptions); + // console.log("Filtered descriptions:", descriptions); // Dispatch closed captions yield put({ type: 'setCaptions', payload: closedcaptions }); // Dispatch descriptions const descriptionData = descriptions; - console.log("Dispatching descriptionData:", descriptionData); + // console.log("Dispatching descriptionData:", descriptionData); yield put.resolve({ type: 'setDescriptions', payload: descriptionData }); // Dispatch final transcript set yield put({ type: 'setTranscript' }); - console.log("Completed setCurrTrans"); + // console.log("Completed setCurrTrans"); }, *setTranscriptions({ payload: trans }, { put, select }) { @@ -180,7 +180,7 @@ export default { yield put({ type: 'media_pause' }); } // Speak out loud - console.log(`SPEAK ${nextDescription.text}`); + // console.log(`SPEAK ${nextDescription.text}`); yield put({ type: 'playerpref/setPreference', payload: { description: nextDescription.text } }) } } @@ -239,7 +239,7 @@ export default { const { watch } = yield select(); // console.log("Entering saveCaption with payload:", { caption, text, begin, end }); - console.log("Current watch state:", watch); + // console.log("Current watch state:", watch); /** * @todo check PROFANITY_LIST @@ -257,39 +257,39 @@ export default { // } if (!text) { - console.log("Exiting saveCaption early: 'text' is falsy."); + // console.log("Exiting saveCaption early: 'text' is falsy."); promptControl.closePrompt(); return; } if (!watch?.currEditing) { - console.log("Exiting saveCaption early: 'watch.currEditing' is falsy."); + // console.log("Exiting saveCaption early: 'watch.currEditing' is falsy."); promptControl.closePrompt(); return; } if (watch.currEditing && watch.currEditing.text === text && watch.currEditing.begin === begin && watch.currEditing.end === end) { - console.log("Exiting saveCaption early: No changes detected in 'currEditing'."); + // console.log("Exiting saveCaption early: No changes detected in 'currEditing'."); promptControl.closePrompt(); return; } - console.log("Updating caption with text:", text); + // console.log("Updating caption with text:", text); caption.text = text; // update data model caption.begin = begin; caption.end = end; promptControl.savingCaption(); // just a ui prompt, empty atm const { id } = watch.currEditing; - console.log("Sending user event with ID:", id, "Current time:", watch.currTime, "Old text:", watch.currEditing.text, "New text:", text); + // console.log("Sending user event with ID:", id, "Current time:", watch.currTime, "Old text:", watch.currEditing.text, "New text:", text); const isClosedCaption = caption.transcription.transcriptionType === 0; - console.log("Is closed caption:", isClosedCaption); + // console.log("Is closed caption:", isClosedCaption); yield put({ type: 'setCurrEditing', payload: null }); try { - console.log("Calling API to update caption line with data:", { id, text, begin, end }); + // console.log("Calling API to update caption line with data:", { id, text, begin, end }); yield call(api.updateCaptionLine, { id, text, begin, end }); if (isClosedCaption) { @@ -301,7 +301,7 @@ export default { } // another elif here for chapter breaks eventually promptControl.savedCaption(isClosedCaption, true); - console.log("Caption saved successfully."); + // console.log("Caption saved successfully."); } catch (error) { console.error("Error saving caption:", error); promptControl.savedCaption(isClosedCaption, false); From c0f089107e3038f7c6d8693efcf8c0e0f33923c7 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Tue, 3 Dec 2024 18:37:53 -0600 Subject: [PATCH 11/12] change CaptionLine to mainly use React state --- .../Transcriptions/CaptionLine/index.js | 134 ++++++++++-------- 1 file changed, 73 insertions(+), 61 deletions(-) diff --git a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js index c5706d26e..506d1bb4d 100644 --- a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js +++ b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js @@ -7,15 +7,18 @@ import './index.scss'; function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { const { text, id, begin, end, kind = "web" } = caption; - const textRef = useRef(); - const timeRef = useRef(); + + const startTimeRef = useRef(); const endTimeRef = useRef(); + const textRef = useRef(); + const [fullBeginTime, setFullBeginTime] = useState(begin); const [fullEndTime, setFullEndTime] = useState(end); + const [savedText, setSavedText] = useState(text); - const [timeString, setTimeString] = useState(prettierTimeStr(begin)); - const [endTimeString, setEndTimeString] = useState(prettierTimeStr(end)); + const [displayedStartTime, setDisplayedStartTime] = useState(prettierTimeStr(begin, false)); + const [displayedEndTime, setDisplayedEndTime] = useState(prettierTimeStr(end, false)); const validateTimeFormat = (input) => { const timeRegex = /^(\d{1,2}:)?\d{1,2}:\d{2}(\.\d+)?$/; @@ -25,68 +28,77 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { return true; }; + // The control flow is that a change to time is saved in handleTimeKeyDown, + // which triggers handleSave, which then changes the displayedTime to the correct truncated time const handleSave = () => { - const newText = textRef.current.innerText; - const newBeginTime = timeRef.current.innerText; - const newEndTime = endTimeRef.current.innerText; - try { - validateTimeFormat(newBeginTime); - validateTimeFormat(newEndTime); - dispatch({ - type: 'watch/saveCaption', - payload: { caption, text: newText, begin: newBeginTime, end: newEndTime }, - }); - setTimeString(prettierTimeStr(newBeginTime)); - setEndTimeString(prettierTimeStr(newEndTime)); - textRef.current.innerText = newText; - } catch (error) { - // TODO: add reactful alert here if timestring is badly formatted - } + dispatch({ + type: 'watch/saveCaption', + payload: { caption, text: savedText, begin: fullBeginTime, end: fullEndTime }, + }); + setDisplayedStartTime(prettierTimeStr(fullBeginTime, false)); + setDisplayedEndTime(prettierTimeStr(fullEndTime, false)); }; + useEffect(() => { + handleSave() + }, [savedText, fullBeginTime, fullEndTime]) + // NOTE: ALL editable text boxes reset the value to the original if the textbox loses focus // Users MUST hit enter for their changes to not be lost - const handleTextBlur = (ref, originalValue) => { - if (ref.current) { - ref.current.innerText = originalValue; - } + const handleTimeBlur = (setDisplayedTime, originalValue) => { + setDisplayedTime(prettierTimeStr(originalValue, false)); }; - const handleTimeBlur = (ref, originalValue) => { - if (ref.current) { - ref.current.innerText = prettierTimeStr(originalValue); + // Ideally, you could do something like setSavedText(savedText), akin to how handleTextKeyDown + // lazy updates savedText, but this won't trigger a DOM update, so we have to do manually + // update the DOM + const handleTextBlur = () => { + if (textRef.current) { + textRef.current.innerText = savedText } }; - const handleFocus = (ref, fullTime) => { - if (ref.current) { - ref.current.innerText = prettierTimeStr(fullTime, true); - dispatch({ - type: 'watch/setTransEditMode', - payload: { caption, innerText: ref.current.innerText }, - }); - } + const handleTimeFocus = (fullTime, setDisplayedTime) => { + setDisplayedTime(prettierTimeStr(fullTime, true)); + dispatch({ + type: 'watch/setTransEditMode', + payload: { caption, innerText: fullTime }, + }); + }; + + const handleTextFocus = () => { + dispatch({ + type: 'watch/setTransEditMode', + payload: { caption }, + }); }; const handleTimeKeyDown = (e, ref, setFullTime) => { - if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { - e.preventDefault(); - const currentTime = ref.current?.innerText || ""; - try { - validateTimeFormat(currentTime); - setFullTime(currentTime); - handleSave(); - } catch (error) { - // TODO: add reactful alert here if timestring is badly formatted + if (ref.current) { + if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { + e.preventDefault(); + const currentValue = ref.current?.innerText || ""; + try { + validateTimeFormat(currentValue); + setFullTime(currentValue); + } catch (error) { + // eslint-disable-next-line no-console + console.log("ERROR", error) + // TODO: add reactful alert here if timestring is badly formatted + } + ref.current.blur(); } - ref.current.blur(); } } const handleTextKeyDown = (e, ref) => { - if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { - handleSave(); - ref.current.blur(); + if (ref.current) { + if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { + e.preventDefault(); + const currentValue = textRef.current?.innerText || ""; + setSavedText(currentValue); + ref.current.blur(); + } } } @@ -100,19 +112,19 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) {
{/* Editable Start Time */}
handleFocus(timeRef, fullBeginTime)} - onBlur={() => handleTimeBlur(timeRef, fullBeginTime)} - onKeyDown={(e) => handleTimeKeyDown(e, timeRef, setFullBeginTime)} + onFocus={() => { handleTimeFocus(fullBeginTime, setDisplayedStartTime) }} + onBlur={() => { handleTimeBlur(setDisplayedStartTime, fullBeginTime) }} + onKeyDown={(e) => { handleTimeKeyDown(e, startTimeRef, setFullBeginTime) }} spellCheck={false} > - {timeString} + {displayedStartTime}
{/* Editable Text */} @@ -124,12 +136,12 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { tabIndex={0} id={`caption-line-textarea-${id}`} className={`caption-line-text-${fontSize}`} - onFocus={() => dispatch({ type: 'watch/setTransEditMode', payload: { caption } })} - onBlur={() => handleTextBlur(textRef, text)} - onKeyDown={(e) => handleTextKeyDown(e, textRef)} spellCheck={false} + onFocus={handleTextFocus} + onBlur={() => { handleTextBlur(text) }} + onKeyDown={(e) => { handleTextKeyDown(e, textRef) }} > - {text} + {savedText}
{/* Editable End Time */} @@ -141,12 +153,12 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { tabIndex={0} id={`caption-line-end-time-${id}`} className="caption-line-time-display" - onFocus={() => handleFocus(endTimeRef, fullEndTime)} - onBlur={() => handleTimeBlur(endTimeRef, fullEndTime)} - onKeyDown={(e) => handleTimeKeyDown(e, endTimeRef, setFullEndTime)} + onFocus={() => { handleTimeFocus(fullEndTime, setDisplayedEndTime) }} + onBlur={() => { handleTimeBlur(setDisplayedEndTime, fullEndTime) }} + onKeyDown={(e) => { handleTimeKeyDown(e, endTimeRef, setFullEndTime) }} spellCheck={false} > - {endTimeString} + {displayedEndTime} From 58bba598deabb173dbe82e5da978b9d2960c6d40 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Wed, 4 Dec 2024 16:38:37 -0600 Subject: [PATCH 12/12] add notifs on timestamp formatting failure, lose focus on ESC pressed --- .../Transcriptions/CaptionLine/index.js | 16 +++++++--- src/screens/Watch/Utils/prompt.control.js | 29 ++++++++++++------- src/screens/Watch/model/trans_effects.js | 4 +++ 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js index 506d1bb4d..5837a79f6 100644 --- a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js +++ b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js @@ -75,16 +75,20 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { const handleTimeKeyDown = (e, ref, setFullTime) => { if (ref.current) { - if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { + if (e.keyCode === KeyCode.KEY_ESCAPE) { + e.preventDefault(); + ref.current.blur(); + } else if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { e.preventDefault(); const currentValue = ref.current?.innerText || ""; try { validateTimeFormat(currentValue); setFullTime(currentValue); } catch (error) { - // eslint-disable-next-line no-console - console.log("ERROR", error) - // TODO: add reactful alert here if timestring is badly formatted + dispatch({ + type: 'watch/timestampFailed', + payload: { caption }, + }); } ref.current.blur(); } @@ -93,6 +97,10 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) { const handleTextKeyDown = (e, ref) => { if (ref.current) { + if (e.keyCode === KeyCode.KEY_ESCAPE) { + ref.current.blur(); + return; + } if (e.keyCode === KeyCode.KEY_RETURN && !e.shiftKey) { e.preventDefault(); const currentValue = textRef.current?.innerText || ""; diff --git a/src/screens/Watch/Utils/prompt.control.js b/src/screens/Watch/Utils/prompt.control.js index d2bc1ff81..97c58035b 100644 --- a/src/screens/Watch/Utils/prompt.control.js +++ b/src/screens/Watch/Utils/prompt.control.js @@ -3,10 +3,10 @@ import { prompt } from 'utils'; /** * Functions for controlling prompts */ -const standardPosition = [70,70]; +const standardPosition = [70, 70]; export const promptControl = { - + closePrompt() { prompt.closeAll(); }, @@ -26,7 +26,7 @@ export const promptControl = { }); }, - savingCaption() {}, + savingCaption() { }, savedCaption(isClosedCaption, success = true) { const captionType = isClosedCaption ? 'Closed Caption' : 'Description' @@ -38,6 +38,16 @@ export const promptControl = { }); }, + timestampFailed(isClosedCaption) { + const captionType = isClosedCaption ? 'Closed Caption' : 'Description' + prompt.addOne({ + status: 'error', + text: `${captionType} could not be saved. Timestamps must be in HH:MM:SS.MS format`, + offset: standardPosition, + timeout: 3000, + }); + }, + hideSecondaryScreen() { prompt.addOne({ text: 'Click video_label to see more screen options.', @@ -49,9 +59,8 @@ export const promptControl = { error(target = 'media data') { const { search, pathname } = window.location; prompt.addOne({ - text: `Couldn't load ${target}. Please refresh to retry.`, + text: `Couldn't load ${target}. Please refresh to retry.`, offset: standardPosition, status: 'error', }); @@ -61,10 +70,10 @@ export const promptControl = { prompt.addOne({ text: `Sorry, if the video can't load, please use Chrome to open the page. Click to open in Chrome`, + window.location.href, + )}&x-source=Safari&x-success=${encodeURIComponent( + window.location.href, + )}`}>Click to open in Chrome`, position: 'top', status: 'error', }); diff --git a/src/screens/Watch/model/trans_effects.js b/src/screens/Watch/model/trans_effects.js index b23acedac..f4e6c5301 100644 --- a/src/screens/Watch/model/trans_effects.js +++ b/src/screens/Watch/model/trans_effects.js @@ -234,6 +234,10 @@ export default { yield put({ type: 'playerpref/setPreference', payload: { showCaptionTips: false } }); } }, + *timestampFailed({ payload: { caption } }) { + promptControl.timestampFailed(caption.transcription.transcriptionType === 0); + yield; + }, // This is a transcript caption *saveCaption({ payload: { caption, text, begin, end } }, { call, put, select }) { const { watch } = yield select();