correct page and word selection

This commit is contained in:
Sara
2023-12-08 16:00:39 +01:00
parent 6e652f7795
commit 2fa11d8571
5 changed files with 298 additions and 1 deletions

View File

@@ -0,0 +1,39 @@
"use client";
import { useState } from "react";
import useTranscript from "../../useTranscript";
import TopicHeader from "./topicHeader";
import TopicWords from "./topicWords";
type TranscriptCorrect = {
params: {
transcriptId: string;
};
};
export default function TranscriptCorrect(details: TranscriptCorrect) {
const transcriptId = details.params.transcriptId;
const transcript = useTranscript(transcriptId);
const [currentTopic, setCurrentTopic] = useState("");
const [selectedTime, setSelectedTime] = useState<{
start: number;
end: number;
}>();
console.log(selectedTime);
return (
<div className="h-full grid grid-cols-2 gap-4">
<div className="flex flex-col h-full">
<TopicHeader
currentTopic={currentTopic}
setCurrentTopic={setCurrentTopic}
transcriptId={transcriptId}
/>
<TopicWords
setSelectedTime={setSelectedTime}
currentTopic={currentTopic}
transcriptId={transcriptId}
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import useTopics from "../../useTopics";
import { useEffect } from "react";
export default function TopicHeader({
currentTopic,
setCurrentTopic,
transcriptId,
}) {
const topics = useTopics(transcriptId);
useEffect(() => {
!topics.loading && setCurrentTopic(topics?.topics?.at(0)?.id);
console.log(currentTopic);
}, [topics.loading]);
if (topics.topics) {
const title = topics.topics.find((topic) => topic.id == currentTopic)
?.title;
const number = topics.topics.findIndex((topic) => topic.id == currentTopic);
const canGoPrevious = number > 0;
const total = topics.topics.length;
const canGoNext = total && number < total + 1;
const onPrev = () =>
setCurrentTopic(topics.topics?.at(number - 1)?.id || "");
const onNext = () =>
setCurrentTopic(topics.topics?.at(number + 1)?.id || "");
return (
<div className="flex flex-row">
<button
className={`w-10 h-10 rounded-full p-2 border border-gray-300 disabled:bg-white ${
canGoPrevious ? "text-gray-500" : "text-gray-300"
}`}
onClick={onPrev}
disabled={!canGoPrevious}
>
<FontAwesomeIcon icon={faArrowLeft} />
</button>
<h1 className="flex-grow">
{title}{" "}
<span>
{number + 1}/{total}
</span>
</h1>
<button
className={`w-10 h-10 rounded-full p-2 border border-gray-300 disabled:bg-white ${
canGoNext ? "text-gray-500" : "text-gray-300"
}`}
onClick={onNext}
disabled={!canGoNext}
>
<FontAwesomeIcon icon={faArrowRight} />
</button>
</div>
);
}
return null;
}

View File

@@ -0,0 +1,127 @@
import { useEffect, useState } from "react";
import useTopicWithWords from "../../useTopicWithWords";
import WaveformLoading from "../../waveformLoading";
type Word = {
end: number;
speaker: number;
start: number;
text: string;
};
type WordBySpeaker = { speaker: number; words: Word[] }[];
const topicWords = ({ setSelectedTime, currentTopic, transcriptId }) => {
const topicWithWords = useTopicWithWords(currentTopic, transcriptId);
const [wordsBySpeaker, setWordsBySpeaker] = useState<WordBySpeaker>();
useEffect(() => {
if (topicWithWords.loading) {
setWordsBySpeaker([]);
setSelectedTime(undefined);
}
}, [topicWithWords.loading]);
useEffect(() => {
if (!topicWithWords.loading && !topicWithWords.error) {
const wordsFlat = topicWithWords.response.words as Word[];
const wordsSorted = wordsFlat.reduce((acc, curr) => {
if (acc.length > 0 && acc[acc.length - 1].speaker == curr.speaker) {
acc[acc.length - 1].words.push(curr);
return acc;
} else {
acc?.push({ speaker: curr.speaker, words: [curr] });
return acc;
}
}, [] as WordBySpeaker);
setWordsBySpeaker(wordsSorted);
}
}, [topicWithWords.response]);
useEffect(() => {
document.onmouseup = (e) => {
let selection = window.getSelection();
if (
selection &&
selection.anchorNode &&
selection.focusNode &&
(selection.anchorNode !== selection.focusNode ||
selection.anchorOffset !== selection.focusOffset)
) {
const anchorNode = selection.anchorNode;
const anchorIsWord =
!!selection.anchorNode.parentElement?.dataset["start"];
const correctedAnchor = anchorIsWord
? anchorNode
: anchorNode.parentNode?.firstChild;
const anchorOffset = anchorIsWord ? 1 : 0;
const focusNode = selection.focusNode;
const focusIsWord = !!selection.focusNode.parentElement?.dataset["end"];
const correctedfocus = focusIsWord
? focusNode
: focusNode.parentNode?.lastChild;
const focusOffset = focusIsWord
? focusNode.textContent?.length
: focusNode.parentNode?.lastChild?.textContent?.length;
if (
correctedAnchor &&
anchorOffset !== undefined &&
correctedfocus &&
focusOffset !== undefined
) {
selection.setBaseAndExtent(
correctedAnchor,
anchorOffset,
correctedfocus,
focusOffset,
);
setSelectedTime({
start:
selection.anchorNode.parentElement?.dataset["start"] ||
(selection.anchorNode.parentElement?.nextElementSibling as any)
?.dataset["start"] ||
0,
end:
selection.focusNode.parentElement?.dataset["end"] ||
(
selection.focusNode.parentElement?.parentElement
?.previousElementSibling?.lastElementChild as any
)?.dataset ||
0,
});
}
}
if (
selection &&
selection.anchorNode &&
selection.focusNode &&
selection.anchorNode == selection.focusNode &&
selection.anchorOffset == selection.focusOffset
) {
setSelectedTime(undefined);
}
};
}, []);
if (!topicWithWords.loading && wordsBySpeaker) {
return (
<div>
{wordsBySpeaker?.map((speakerWithWords) => (
<p>
<span>Speaker&nbsp;{speakerWithWords.speaker}&nbsp;:&nbsp;</span>
{speakerWithWords.words.map((word) => (
<span data-start={word.start} data-end={word.end}>
{word.text}
</span>
))}
</p>
))}
</div>
);
}
if (topicWithWords.loading) return <WaveformLoading />;
if (topicWithWords.error) return <p>error</p>;
return null;
};
export default topicWords;

View File

@@ -0,0 +1,71 @@
import { useEffect, useState } from "react";
import { V1TranscriptGetTopicsWithWordsRequest } from "../../api/apis/DefaultApi";
import { GetTranscript, GetTranscriptTopicWithWords } from "../../api";
import { useError } from "../../(errors)/errorContext";
import getApi from "../../lib/getApi";
import { shouldShowError } from "../../lib/errorUtils";
type ErrorTopicWithWords = {
error: Error;
loading: false;
response: any;
};
type LoadingTopicWithWords = {
response: any;
loading: true;
error: false;
};
type SuccessTopicWithWords = {
response: GetTranscriptTopicWithWords;
loading: false;
error: null;
};
const useTopicWithWords = (
topicId: string | null,
transcriptId: string,
): ErrorTopicWithWords | LoadingTopicWithWords | SuccessTopicWithWords => {
const [response, setResponse] = useState<GetTranscript | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = getApi();
useEffect(() => {
setLoading(true);
}, [transcriptId, topicId]);
useEffect(() => {
if (!transcriptId || !topicId || !api) return;
setLoading(true);
const requestParameters: V1TranscriptGetTopicsWithWordsRequest = {
transcriptId,
};
api
.v1TranscriptGetTopicsWithWords(requestParameters)
.then((result) => {
setResponse(result.find((topic) => topic.id == topicId));
setLoading(false);
console.debug("Topics with words Loaded:", result);
})
.catch((error) => {
const shouldShowHuman = shouldShowError(error);
if (shouldShowHuman) {
setError(error, "There was an error loading the topics with words");
} else {
setError(error);
}
setErrorState(error);
});
}, [transcriptId, !api, topicId]);
return { response, loading, error } as
| ErrorTopicWithWords
| LoadingTopicWithWords
| SuccessTopicWithWords;
};
export default useTopicWithWords;

View File

@@ -14,7 +14,7 @@ type TranscriptTopics = {
const useTopics = (id: string): TranscriptTopics => {
const [topics, setTopics] = useState<Topic[] | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = getApi();