diff --git a/src/renderer/src/components/Discussions/CommentEditor.tsx b/src/renderer/src/components/Discussions/CommentEditor.tsx index 6eee00df..a9f8206a 100644 --- a/src/renderer/src/components/Discussions/CommentEditor.tsx +++ b/src/renderer/src/components/Discussions/CommentEditor.tsx @@ -1,4 +1,5 @@ import { + Box, Button, TextField, Tooltip, @@ -23,11 +24,15 @@ const RowDiv = styled('div')(() => ({ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', + flexWrap: 'wrap', + minWidth: 0, })); const ColumnDiv = styled('div')(() => ({ display: 'flex', flexDirection: 'column', + minWidth: 0, + width: '100%', })); const StatusMessage = styled(Typography)(({ theme }) => ({ @@ -49,6 +54,7 @@ interface IProps extends IStateProps { onCancel?: () => void; setCanSaveRecording: (canSave: boolean) => void; onTextChange: (txt: string) => void; + onAudioDraftChange?: (hasDraft: boolean) => void; } export const CommentEditor = (props: IProps) => { const { @@ -63,6 +69,7 @@ export const CommentEditor = (props: IProps) => { onCancel, setCanSaveRecording, onTextChange, + onAudioDraftChange, } = props; const { playing, @@ -85,6 +92,7 @@ export const CommentEditor = (props: IProps) => { const doRecordRef = useRef(false); const [recording, setRecording] = useState(false); const [myChanged, setMyChanged] = useState(false); + const [showRecorder, setShowRecorder] = useState(false); const { commentId } = useArtifactType(); const { @@ -97,6 +105,16 @@ export const CommentEditor = (props: IProps) => { isChanged, } = useContext(UnsavedContext).state; + const setAudioDraft = (hasDraft: boolean) => { + onAudioDraftChange?.(hasDraft); + }; + + const clearUnsavedIfEmpty = () => { + if (!curText.length && !canSaveRef.current) { + toolChanged(toolId, false); + } + }; + useEffect(() => { return () => { if (doRecordRef.current) setCommentRecording(false); @@ -123,11 +141,13 @@ export const CommentEditor = (props: IProps) => { 100 ).then(() => { doRecordRef.current = true; + setShowRecorder(true); setStartRecord(false); }); } catch { //do it anyway... doRecordRef.current = true; + setShowRecorder(true); setStartRecord(false); } }, [startRecord, playing, itemPlaying, commentPlaying]); @@ -137,11 +157,19 @@ export const CommentEditor = (props: IProps) => { canSaveRef.current = valid; setCanSave(valid); setCanSaveRecording(valid); + setAudioDraft(valid); if (valid) toolChanged(toolId, true); + else clearUnsavedIfEmpty(); } }; const onRecording = (r: boolean) => { setRecording(r); + if (r) { + toolChanged(toolId, true); + } else if (doRecordRef.current) { + // Paused/stopped: enable parent Add before blob-ready/canSave propagates (TT-7216). + setAudioDraft(true); + } }; const handleTextChange = (e: any) => { setCurText(e.target.value); @@ -163,6 +191,7 @@ export const CommentEditor = (props: IProps) => { }; const handleRecord = () => { + toolChanged(toolId, true); setStartRecord(true); setCommentRecording(true); }; @@ -172,7 +201,13 @@ export const CommentEditor = (props: IProps) => { setStatusText(''); setCurText(''); doRecordRef.current = false; + setShowRecorder(false); + canSaveRef.current = false; + setCanSave(false); + setCanSaveRecording(false); + setAudioDraft(false); clearCompleted(toolId); + clearUnsavedIfEmpty(); }; useEffect(() => { @@ -182,6 +217,81 @@ export const CommentEditor = (props: IProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [refresh]); + const recorderNode = ( + + + + ); + + const actionButtons = + onOk && + (!cancelOnlyIfChanged || doRecordRef.current || myChanged) && ( + + + + + + + + + + + + + ); + return ( { label={t.comment} focused /> - {doRecordRef.current ? ( - <> + {showRecorder ? ( + + {recorderNode} - -
-
- {onOk && - (!cancelOnlyIfChanged || - doRecordRef.current || - myChanged) && ( - - - - - - )} - {onOk && ( - - - - - - )} -
- {statusText} -
+ {actionButtons} + {statusText}
- +
) : ( @@ -303,37 +333,7 @@ export const CommentEditor = (props: IProps) => {
{statusText} - {onOk && - (!cancelOnlyIfChanged || doRecordRef.current || myChanged) && ( - - - - - - )} - {onOk && ( - - - - - - )} + {actionButtons}
)} diff --git a/src/renderer/src/components/Discussions/DiscussionCard.tsx b/src/renderer/src/components/Discussions/DiscussionCard.tsx index a6b196d4..87918b8d 100644 --- a/src/renderer/src/components/Discussions/DiscussionCard.tsx +++ b/src/renderer/src/components/Discussions/DiscussionCard.tsx @@ -60,7 +60,12 @@ import { } from '../../crud/useArtifactCategory'; import { LightTooltip, StageReport } from '../../control'; import { PassageDetailContext } from '../../context/PassageDetailContext'; -import { removeExtension, startEnd, useWaitForRemoteQueue } from '../../utils'; +import { + removeExtension, + startEnd, + useWaitForRemoteQueue, + waitForIt, +} from '../../utils'; import { useOrgWorkflowSteps } from '../../crud/useOrgWorkflowSteps'; import { NewDiscussionToolId, NewCommentToolId } from './DiscussionList'; import { SaveInfo, UnsavedContext } from '../../context/UnsavedContext'; @@ -277,8 +282,14 @@ export const DiscussionCard = (props: IProps) => { const [comment, setComment] = useState(''); const commentMediaId = useRef(undefined); const [canSaveRecording, setCanSaveRecording] = useState(false); + const canSaveRecordingRef = useRef(false); + const [hasAudioDraft, setHasAudioDraft] = useState(false); const { userIsAdmin } = useRole(); + useEffect(() => { + canSaveRecordingRef.current = canSaveRecording; + }, [canSaveRecording]); + const CommentAuthor = (comment: CommentD) => getMentorAuthor(comment.attributes.visible) ?? related(comment, 'creatorUser') ?? @@ -357,6 +368,7 @@ export const DiscussionCard = (props: IProps) => { setEditCategory(''); setComment(''); commentText.current = ''; + setHasAudioDraft(false); setMoveTo(undefined); }; @@ -784,7 +796,24 @@ export const DiscussionCard = (props: IProps) => { //if there is an audio comment, start the upload if (cardSavingRef.current) return; cardSavingRef.current = true; - if (canSaveRecording && !commentMediaId.current) { + const hasPendingAudio = + (canSaveRecording || hasAudioDraft) && !commentMediaId.current; + if (hasPendingAudio) { + // hasAudioDraft is set on pause before canSaveRecording; MediaRecord + // completes save immediately if startSave runs without a blob. + if (hasAudioDraft && !canSaveRecordingRef.current) { + try { + await waitForIt( + 'audio comment blob ready', + () => canSaveRecordingRef.current, + () => false, + 30 + ); + } catch { + cardSavingRef.current = false; + return; + } + } startSave(NewCommentToolId); //we'll do the rest in afterUpload return; @@ -801,6 +830,7 @@ export const DiscussionCard = (props: IProps) => { cardSavingRef.current = true; commentText.current = ''; commentMediaId.current = ''; + setHasAudioDraft(false); onAddComplete && onAddComplete(''); setEditing(false); setChanged(false); @@ -968,6 +998,7 @@ export const DiscussionCard = (props: IProps) => { comment={commentText.current} refresh={refresh} setCanSaveRecording={setCanSaveRecording} + onAudioDraftChange={setHasAudioDraft} fileName={fileName(editSubject, '')} afterUploadCb={afterUploadCb} passageId={passageId} @@ -985,10 +1016,19 @@ export const DiscussionCard = (props: IProps) => { segSavingRef.current || !mediafileId || editSubject === '' || - !(canSaveRecording || myComments.length > 0 || comment) + !( + canSaveRecording || + hasAudioDraft || + myComments.length > 0 || + comment + ) } > - {discussion.id ? ts.save : t.addComment} + {onAddComplete + ? t.addComment + : discussion.id + ? ts.save + : t.addComment}