From b4322c22cca38350ea6fbf1900e4409cfd3361bc Mon Sep 17 00:00:00 2001 From: Sara Date: Wed, 13 Dec 2023 12:49:36 +0100 Subject: [PATCH] error handling and clean up --- .../[transcriptId]/correct/page.tsx | 86 ++++----- .../correct/participantList.tsx | 173 +++++++++++------- .../[transcriptId]/correct/topicPlayer.tsx | 31 ++-- .../[transcriptId]/correct/topicWords.tsx | 23 +-- .../[transcriptId]/correct/types.ts | 20 ++ 5 files changed, 183 insertions(+), 150 deletions(-) create mode 100644 www/app/[domain]/transcripts/[transcriptId]/correct/types.ts diff --git a/www/app/[domain]/transcripts/[transcriptId]/correct/page.tsx b/www/app/[domain]/transcripts/[transcriptId]/correct/page.tsx index 617dac10..9dcb2c7e 100644 --- a/www/app/[domain]/transcripts/[transcriptId]/correct/page.tsx +++ b/www/app/[domain]/transcripts/[transcriptId]/correct/page.tsx @@ -1,6 +1,5 @@ "use client"; -import { useEffect, useState } from "react"; -import useTranscript from "../../useTranscript"; +import { useState } from "react"; import TopicHeader from "./topicHeader"; import TopicWords from "./topicWords"; import TopicPlayer from "./topicPlayer"; @@ -8,6 +7,7 @@ import useParticipants from "../../useParticipants"; import useTopicWithWords from "../../useTopicWithWords"; import ParticipantList from "./participantList"; import { GetTranscriptTopic } from "../../../../api"; +import { SelectedText, selectedTextIsTimeSlice } from "./types"; export type TranscriptCorrect = { params: { @@ -15,48 +15,15 @@ export type TranscriptCorrect = { }; }; -export type TimeSlice = { - start: number; - end: number; -}; - -export type SelectedText = number | TimeSlice | undefined; - -export function selectedTextIsSpeaker( - selectedText: SelectedText, -): selectedText is number { - return typeof selectedText == "number"; -} -export function selectedTextIsTimeSlice( - selectedText: SelectedText, -): selectedText is TimeSlice { - return ( - typeof (selectedText as any)?.start == "number" && - typeof (selectedText as any)?.end == "number" - ); -} - -export default function TranscriptCorrect(details: TranscriptCorrect) { - const transcriptId = details.params.transcriptId; +export default function TranscriptCorrect({ + params: { transcriptId }, +}: TranscriptCorrect) { const stateCurrentTopic = useState(); const [currentTopic, _sct] = stateCurrentTopic; - const topicWithWords = useTopicWithWords(currentTopic?.id, transcriptId); - - const [topicTime, setTopicTime] = useState(); - const participants = useParticipants(transcriptId); const stateSelectedText = useState(); const [selectedText, _sst] = stateSelectedText; - - useEffect(() => { - if (currentTopic) { - setTopicTime({ - start: currentTopic.timestamp, - end: currentTopic.timestamp + currentTopic.duration, - }); - } else { - setTopicTime(undefined); - } - }, [currentTopic]); + const topicWithWords = useTopicWithWords(currentTopic?.id, transcriptId); + const participants = useParticipants(transcriptId); return (
@@ -73,21 +40,30 @@ export default function TranscriptCorrect(details: TranscriptCorrect) { />
- - + {currentTopic ? ( + + ) : ( +
+ )} + {participants.response && ( + + )}
); diff --git a/www/app/[domain]/transcripts/[transcriptId]/correct/participantList.tsx b/www/app/[domain]/transcripts/[transcriptId]/correct/participantList.tsx index 69412e11..fe06653e 100644 --- a/www/app/[domain]/transcripts/[transcriptId]/correct/participantList.tsx +++ b/www/app/[domain]/transcripts/[transcriptId]/correct/participantList.tsx @@ -4,7 +4,8 @@ import { ChangeEvent, useEffect, useRef, useState } from "react"; import { Participant } from "../../../../api"; import getApi from "../../../../lib/getApi"; import { UseParticipants } from "../../useParticipants"; -import { selectedTextIsSpeaker, selectedTextIsTimeSlice } from "./page"; +import { selectedTextIsSpeaker, selectedTextIsTimeSlice } from "./types"; +import { useError } from "../../../../(errors)/errorContext"; type ParticipantList = { participants: UseParticipants; @@ -13,6 +14,7 @@ type ParticipantList = { stateSelectedText: any; }; // NTH re-order list when searching +// HTH case-insensitive matching const ParticipantList = ({ transcriptId, participants, @@ -20,7 +22,7 @@ const ParticipantList = ({ stateSelectedText, }: ParticipantList) => { const api = getApi(); - + const { setError } = useError(); const [loading, setLoading] = useState(false); const [participantInput, setParticipantInput] = useState(""); const inputRef = useRef(null); @@ -40,40 +42,42 @@ const ParticipantList = ({ ); if (participant) { setParticipantInput(participant.name); + setOneMatch(undefined); setSelectedParticipant(participant); - inputRef.current?.focus(); setAction("Rename"); } else if (!selectedParticipant) { setSelectedParticipant(undefined); setParticipantInput(""); - inputRef.current?.focus(); + setOneMatch(undefined); setAction("Create to rename"); } } if (selectedTextIsTimeSlice(selectedText)) { - setParticipantInput(""); inputRef.current?.focus(); + setParticipantInput(""); + setOneMatch(undefined); setAction("Create and assign"); setSelectedParticipant(undefined); } if (typeof selectedText == undefined) { + inputRef.current?.blur(); setAction(null); } } - }, [selectedText, participants]); + }, [selectedText, participants.response]); useEffect(() => { document.onkeyup = (e) => { if (loading || participants.loading || topicWithWords.loading) return; if (e.key === "Enter" && e.ctrlKey) { if (oneMatch) { - if (action == "Create and assign") { - assignTo(oneMatch)(); - setOneMatch(undefined); - setParticipantInput(""); + if ( + action == "Create and assign" && + selectedTextIsTimeSlice(selectedText) + ) { + assignTo(oneMatch)().catch(() => {}); } else if ( action == "Create to rename" && - oneMatch && selectedTextIsSpeaker(selectedText) ) { mergeSpeaker(selectedText, oneMatch)(); @@ -85,33 +89,78 @@ const ParticipantList = ({ }; }); + const onSuccess = () => { + topicWithWords.refetch(); + participants.refetch(); + setLoading(false); + setAction(null); + setSelectedText(undefined); + setSelectedParticipant(undefined); + setParticipantInput(""); + setOneMatch(undefined); + inputRef?.current?.blur(); + }; + + const assignTo = + (participant) => async (e?: React.MouseEvent) => { + e?.preventDefault(); + e?.stopPropagation(); + + if (loading || participants.loading || topicWithWords.loading) return; + if (!selectedTextIsTimeSlice(selectedText)) return; + + setLoading(true); + try { + await api?.v1TranscriptAssignSpeaker({ + speakerAssignment: { + participant: participant.id, + timestampFrom: selectedText.start, + timestampTo: selectedText.end, + }, + transcriptId, + }); + onSuccess(); + } catch (error) { + setError(error, "There was an error assigning"); + setLoading(false); + throw error; + } + }; + const mergeSpeaker = (speakerFrom, participantTo: Participant) => async () => { if (loading || participants.loading || topicWithWords.loading) return; + setLoading(true); if (participantTo.speaker) { - setLoading(true); - await api?.v1TranscriptMergeSpeaker({ - transcriptId, - speakerMerge: { - speakerFrom: speakerFrom, - speakerTo: participantTo.speaker, - }, - }); + try { + await api?.v1TranscriptMergeSpeaker({ + transcriptId, + speakerMerge: { + speakerFrom: speakerFrom, + speakerTo: participantTo.speaker, + }, + }); + onSuccess(); + } catch (error) { + setError(error, "There was an error merging"); + setLoading(false); + } } else { - await api?.v1TranscriptUpdateParticipant({ - transcriptId, - participantId: participantTo.id, - updateParticipant: { speaker: speakerFrom }, - }); + try { + await api?.v1TranscriptUpdateParticipant({ + transcriptId, + participantId: participantTo.id, + updateParticipant: { speaker: speakerFrom }, + }); + onSuccess(); + } catch (error) { + setError(error, "There was an error merging (update)"); + setLoading(false); + } } - participants.refetch(); - topicWithWords.refetch(); - setAction(null); - setParticipantInput(""); - setLoading(false); }; - const doAction = (e?) => { + const doAction = async (e?) => { e?.preventDefault(); e?.stopPropagation(); if ( @@ -138,6 +187,10 @@ const ParticipantList = ({ .then(() => { participants.refetch(); setLoading(false); + }) + .catch((e) => { + setError(e, "There was an error renaming"); + setLoading(false); }); } } else if ( @@ -156,6 +209,11 @@ const ParticipantList = ({ .then(() => { participants.refetch(); setParticipantInput(""); + setOneMatch(undefined); + setLoading(false); + }) + .catch((e) => { + setError(e, "There was an error creating"); setLoading(false); }); } else if ( @@ -163,18 +221,22 @@ const ParticipantList = ({ selectedTextIsTimeSlice(selectedText) ) { setLoading(true); - api - ?.v1TranscriptAddParticipant({ + try { + const participant = await api?.v1TranscriptAddParticipant({ createParticipant: { name: participantInput, }, transcriptId, - }) - .then((participant) => { - setLoading(false); - assignTo(participant)(); - setParticipantInput(""); }); + setLoading(false); + assignTo(participant)().catch(() => { + // error and loading are handled by assignTo catch + participants.refetch(); + }); + } catch (error) { + setError(e, "There was an error creating"); + setLoading(false); + } } else if (action == "Create") { setLoading(true); api @@ -188,6 +250,10 @@ const ParticipantList = ({ participants.refetch(); setParticipantInput(""); setLoading(false); + }) + .catch((e) => { + setError(e, "There was an error creating"); + setLoading(false); }); } }; @@ -204,41 +270,19 @@ const ParticipantList = ({ .then(() => { participants.refetch(); setLoading(false); + }) + .catch((e) => { + setError(e, "There was an error deleting"); + setLoading(false); }); }; - const assignTo = - (participant) => (e?: React.MouseEvent) => { - e?.preventDefault(); - e?.stopPropagation(); - if (loading || participants.loading || topicWithWords.loading) return; - if (!selectedTextIsTimeSlice(selectedText)) return; - - setLoading(true); - api - ?.v1TranscriptAssignSpeaker({ - speakerAssignment: { - participant: participant.id, - timestampFrom: selectedText.start, - timestampTo: selectedText.end, - }, - transcriptId, - }) - .then(() => { - topicWithWords.refetch(); - participants.refetch(); - setLoading(false); - setAction(null); - setSelectedText(undefined); - setSelectedParticipant(undefined); - }); - }; - const selectParticipant = (participant) => (e) => { setSelectedParticipant(participant); setSelectedText(participant.speaker); setAction("Rename"); setParticipantInput(participant.name); + oneMatch && setOneMatch(undefined); }; const clearSelection = () => { @@ -246,6 +290,7 @@ const ParticipantList = ({ setSelectedText(undefined); setAction(null); setParticipantInput(""); + oneMatch && setOneMatch(undefined); }; const preventClick = (e) => { e?.stopPropagation(); diff --git a/www/app/[domain]/transcripts/[transcriptId]/correct/topicPlayer.tsx b/www/app/[domain]/transcripts/[transcriptId]/correct/topicPlayer.tsx index 30ad7392..932c8ee6 100644 --- a/www/app/[domain]/transcripts/[transcriptId]/correct/topicPlayer.tsx +++ b/www/app/[domain]/transcripts/[transcriptId]/correct/topicPlayer.tsx @@ -2,8 +2,19 @@ import { useEffect, useRef, useState } from "react"; import useMp3 from "../../useMp3"; import { formatTime } from "../../../../lib/time"; import SoundWaveCss from "./soundWaveCss"; +import { TimeSlice } from "./types"; -const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => { +type TopicPlayer = { + transcriptId: string; + selectedTime: TimeSlice | undefined; + topicTime: TimeSlice; +}; + +const TopicPlayer = ({ + transcriptId, + selectedTime, + topicTime, +}: TopicPlayer) => { const mp3 = useMp3(transcriptId); const [isPlaying, setIsPlaying] = useState(false); const [endTopicCallback, setEndTopicCallback] = useState<() => void>(); @@ -56,11 +67,7 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => { setEndTopicCallback( () => function () { - if ( - mp3.media && - topicTime.end && - mp3.media.currentTime >= topicTime.end - ) { + if (mp3.media && mp3.media.currentTime >= topicTime.end) { mp3.media.pause(); setIsPlaying(false); mp3.media.currentTime = topicTime.start; @@ -68,7 +75,7 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => { } }, ); - if (mp3.media && topicTime) { + if (mp3.media) { playButton.current?.focus(); mp3.media?.pause(); // there's no callback on pause but apparently changing the time while palying doesn't work... so here is a timeout @@ -80,7 +87,7 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => { }, 10); setIsPlaying(false); } - }, [topicTime, mp3.media]); + }, [!mp3.media, topicTime.start, topicTime.end]); useEffect(() => { endTopicCallback && @@ -164,11 +171,9 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
{showTime}
- {topicTime && ( - - )} + {!isPlaying ? (