mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
UI improvements
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { faArrowTurnDown, faSpinner } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowTurnDown, faSpinner } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||||
import { Participant } from "../../../../api";
|
import { Participant } from "../../../../api";
|
||||||
import getApi from "../../../../lib/getApi";
|
import getApi from "../../../../lib/getApi";
|
||||||
import { UseParticipants } from "../../useParticipants";
|
import { UseParticipants } from "../../useParticipants";
|
||||||
@@ -60,30 +60,7 @@ const ParticipantList = ({
|
|||||||
setAction(null);
|
setAction(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedText]);
|
}, [selectedText, participants]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
participants.response &&
|
|
||||||
(action == "Create and assign" || action == "Create to rename")
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
participants.response.filter((p) => p.name.startsWith(participantInput))
|
|
||||||
.length == 1
|
|
||||||
) {
|
|
||||||
setOneMatch(
|
|
||||||
participants.response.find((p) =>
|
|
||||||
p.name.startsWith(participantInput),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setOneMatch(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (participantInput && !action) {
|
|
||||||
setAction("Create");
|
|
||||||
}
|
|
||||||
}, [participantInput]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.onkeyup = (e) => {
|
document.onkeyup = (e) => {
|
||||||
@@ -251,6 +228,9 @@ const ParticipantList = ({
|
|||||||
topicWithWords.refetch();
|
topicWithWords.refetch();
|
||||||
participants.refetch();
|
participants.refetch();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
setAction(null);
|
||||||
|
setSelectedText(undefined);
|
||||||
|
setSelectedParticipant(undefined);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -271,23 +251,48 @@ const ParticipantList = ({
|
|||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
e?.preventDefault();
|
e?.preventDefault();
|
||||||
};
|
};
|
||||||
|
const changeParticipantInput = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value.replaceAll(/,|\.| /g, "");
|
||||||
|
setParticipantInput(value);
|
||||||
|
if (
|
||||||
|
value.length > 0 &&
|
||||||
|
participants.response &&
|
||||||
|
(action == "Create and assign" || action == "Create to rename")
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
participants.response.filter((p) => p.name.startsWith(value)).length ==
|
||||||
|
1
|
||||||
|
) {
|
||||||
|
setOneMatch(
|
||||||
|
participants.response.find((p) => p.name.startsWith(value)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setOneMatch(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value.length > 0 && !action) {
|
||||||
|
setAction("Create");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full" onClick={clearSelection}>
|
<div className="h-full" onClick={clearSelection}>
|
||||||
<div onClick={preventClick}>
|
<div onClick={preventClick}>
|
||||||
<div>
|
<div className="grid grid-cols-2 gap-2 mb-2">
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
onChange={(e) => setParticipantInput(e.target.value)}
|
onChange={changeParticipantInput}
|
||||||
value={participantInput}
|
value={participantInput}
|
||||||
|
className="border border-blue-400 p-1"
|
||||||
/>
|
/>
|
||||||
{action && (
|
{action && (
|
||||||
<button onClick={doAction}>
|
<button onClick={doAction} className="p-2 bg-blue-200 w-full">
|
||||||
|
[
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faArrowTurnDown}
|
icon={faArrowTurnDown}
|
||||||
className="rotate-90 mr-2"
|
className="rotate-90 h-2"
|
||||||
/>
|
/>
|
||||||
{action}
|
]{" " + action}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -306,14 +311,14 @@ const ParticipantList = ({
|
|||||||
<li
|
<li
|
||||||
onClick={selectParticipant(participant)}
|
onClick={selectParticipant(participant)}
|
||||||
className={
|
className={
|
||||||
"flex flex-row justify-between " +
|
"flex flex-row justify-between border-b last:border-b-0 py-2 " +
|
||||||
(participantInput.length > 0 &&
|
(participantInput.length > 0 &&
|
||||||
selectedText &&
|
selectedText &&
|
||||||
participant.name.startsWith(participantInput)
|
participant.name.startsWith(participantInput)
|
||||||
? "bg-blue-100 "
|
? "bg-blue-100 "
|
||||||
: "") +
|
: "") +
|
||||||
(participant.id == selectedParticipant?.id
|
(participant.id == selectedParticipant?.id
|
||||||
? "border-blue-400 border"
|
? "bg-blue-200 border"
|
||||||
: "")
|
: "")
|
||||||
}
|
}
|
||||||
key={participant.id}
|
key={participant.id}
|
||||||
@@ -324,41 +329,52 @@ const ParticipantList = ({
|
|||||||
{selectedTextIsSpeaker(selectedText) &&
|
{selectedTextIsSpeaker(selectedText) &&
|
||||||
!selectedParticipant &&
|
!selectedParticipant &&
|
||||||
!loading && (
|
!loading && (
|
||||||
<button onClick={mergeSpeaker(selectedText, participant)}>
|
<button
|
||||||
{oneMatch &&
|
onClick={mergeSpeaker(selectedText, participant)}
|
||||||
action == "Create to rename" &&
|
className="bg-blue-400 px-2 ml-2"
|
||||||
participant.name.startsWith(participantInput) && (
|
>
|
||||||
|
{oneMatch?.id == participant.id &&
|
||||||
|
action == "Create to rename" && (
|
||||||
<>
|
<>
|
||||||
{" "}
|
<span className="text-xs">
|
||||||
<span>CTRL + </span>{" "}
|
[CTRL +{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faArrowTurnDown}
|
icon={faArrowTurnDown}
|
||||||
className="rotate-90 mr-2"
|
className="rotate-90 mr-2 h-2"
|
||||||
/>{" "}
|
/>
|
||||||
|
]
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
)}{" "}
|
)}{" "}
|
||||||
Merge
|
Merge
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{selectedTextIsTimeSlice(selectedText) && !loading && (
|
{selectedTextIsTimeSlice(selectedText) && !loading && (
|
||||||
<button onClick={assignTo(participant)}>
|
<button
|
||||||
{oneMatch &&
|
onClick={assignTo(participant)}
|
||||||
action == "Create and assign" &&
|
className="bg-blue-400 px-2 ml-2"
|
||||||
participant.name.startsWith(participantInput) && (
|
>
|
||||||
|
{oneMatch?.id == participant.id &&
|
||||||
|
action == "Create and assign" && (
|
||||||
<>
|
<>
|
||||||
{" "}
|
<span className="text-xs">
|
||||||
<span>CTRL + </span>{" "}
|
[CTRL +{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faArrowTurnDown}
|
icon={faArrowTurnDown}
|
||||||
className="rotate-90 mr-2"
|
className="rotate-90 mr-2 h-2"
|
||||||
/>{" "}
|
/>
|
||||||
|
]
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
)}{" "}
|
)}{" "}
|
||||||
Assign
|
Assign
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button onClick={deleteParticipant(participant.id)}>
|
<button
|
||||||
|
onClick={deleteParticipant(participant.id)}
|
||||||
|
className="bg-blue-400 px-2 ml-2"
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export default ({ playing }) => (
|
||||||
|
<div className="flex justify-between w-16 h-8 m-auto">
|
||||||
|
<div
|
||||||
|
className={`bg-blue-400 rounded w-2 ${
|
||||||
|
playing ? "animate-wave-quiet" : ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`bg-blue-400 rounded w-2 ${
|
||||||
|
playing ? "animate-wave-normal" : ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`bg-blue-400 rounded w-2 ${
|
||||||
|
playing ? "animate-wave-quiet" : ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`bg-blue-400 rounded w-2 ${
|
||||||
|
playing ? "animate-wave-loud" : ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`bg-blue-400 rounded w-2 ${
|
||||||
|
playing ? "animate-wave-normal" : ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useMp3 from "../../useMp3";
|
import useMp3 from "../../useMp3";
|
||||||
|
import { formatTime } from "../../../../lib/time";
|
||||||
|
import SoundWaveCss from "./soundWaveCss";
|
||||||
|
|
||||||
const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
||||||
const mp3 = useMp3(transcriptId);
|
const mp3 = useMp3(transcriptId);
|
||||||
@@ -7,9 +9,10 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
|||||||
const [endTopicCallback, setEndTopicCallback] = useState<() => void>();
|
const [endTopicCallback, setEndTopicCallback] = useState<() => void>();
|
||||||
const [endSelectionCallback, setEndSelectionCallback] =
|
const [endSelectionCallback, setEndSelectionCallback] =
|
||||||
useState<() => void>();
|
useState<() => void>();
|
||||||
|
const [showTime, setShowTime] = useState("");
|
||||||
|
|
||||||
const keyHandler = (e) => {
|
const keyHandler = (e) => {
|
||||||
if (e.key == "!") {
|
if (e.key == " ") {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
mp3.media?.pause();
|
mp3.media?.pause();
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
@@ -17,6 +20,8 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
|||||||
mp3.media?.play();
|
mp3.media?.play();
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
}
|
}
|
||||||
|
} else if (selectedTime && e.key == ",") {
|
||||||
|
playSelection();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -26,6 +31,24 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const calcShowTime = () => {
|
||||||
|
setShowTime(
|
||||||
|
`${
|
||||||
|
mp3.media?.currentTime
|
||||||
|
? formatTime(mp3.media?.currentTime - topicTime.start)
|
||||||
|
: "00:00"
|
||||||
|
}/${formatTime(topicTime.end - topicTime.start)}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let i;
|
||||||
|
if (isPlaying) {
|
||||||
|
i = setInterval(calcShowTime, 1000);
|
||||||
|
}
|
||||||
|
return () => i && clearInterval(i);
|
||||||
|
}, [isPlaying]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEndTopicCallback(
|
setEndTopicCallback(
|
||||||
() =>
|
() =>
|
||||||
@@ -38,6 +61,7 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
|||||||
mp3.media.pause();
|
mp3.media.pause();
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
mp3.media.currentTime = topicTime.start;
|
mp3.media.currentTime = topicTime.start;
|
||||||
|
calcShowTime();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -47,6 +71,7 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (mp3.media) {
|
if (mp3.media) {
|
||||||
mp3.media.currentTime = topicTime.start;
|
mp3.media.currentTime = topicTime.start;
|
||||||
|
setShowTime(`00:00/${formatTime(topicTime.end - topicTime.start)}`);
|
||||||
}
|
}
|
||||||
}, 10);
|
}, 10);
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
@@ -65,9 +90,12 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
|||||||
};
|
};
|
||||||
}, [endTopicCallback]);
|
}, [endTopicCallback]);
|
||||||
|
|
||||||
const playSelection = () => {
|
const playSelection = (e?) => {
|
||||||
|
e?.preventDefault();
|
||||||
|
e?.target?.blur();
|
||||||
if (mp3.media && selectedTime?.start !== undefined) {
|
if (mp3.media && selectedTime?.start !== undefined) {
|
||||||
mp3.media.currentTime = selectedTime.start;
|
mp3.media.currentTime = selectedTime.start;
|
||||||
|
calcShowTime();
|
||||||
setEndSelectionCallback(
|
setEndSelectionCallback(
|
||||||
() =>
|
() =>
|
||||||
function () {
|
function () {
|
||||||
@@ -99,7 +127,9 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
|||||||
};
|
};
|
||||||
}, [endSelectionCallback]);
|
}, [endSelectionCallback]);
|
||||||
|
|
||||||
const playTopic = () => {
|
const playTopic = (e) => {
|
||||||
|
e?.preventDefault();
|
||||||
|
e?.target?.blur();
|
||||||
if (mp3.media) {
|
if (mp3.media) {
|
||||||
mp3.media.currentTime = topicTime.start;
|
mp3.media.currentTime = topicTime.start;
|
||||||
mp3.media.play();
|
mp3.media.play();
|
||||||
@@ -109,28 +139,46 @@ const TopicPlayer = ({ transcriptId, selectedTime, topicTime }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const playCurrent = () => {
|
const playCurrent = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e?.target?.blur();
|
||||||
|
|
||||||
mp3.media?.play();
|
mp3.media?.play();
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pause = (e) => {
|
const pause = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e?.target?.blur();
|
||||||
|
|
||||||
mp3.media?.pause();
|
mp3.media?.pause();
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mp3.media) {
|
if (mp3.media) {
|
||||||
return (
|
return (
|
||||||
<div id="audioContainer">
|
<div className="mb-4 grid grid-cols-3 gap-2">
|
||||||
|
<SoundWaveCss playing={isPlaying} />
|
||||||
|
<div className="col-span-2">{showTime}</div>
|
||||||
|
{topicTime && (
|
||||||
|
<button className="p-2 bg-blue-200 w-full" onClick={playTopic}>
|
||||||
|
Play From Start
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{!isPlaying ? (
|
{!isPlaying ? (
|
||||||
<button onClick={playCurrent}>Play</button>
|
<button className="p-2 bg-blue-200 w-full" onClick={playCurrent}>
|
||||||
|
<span className="text-xs">[SPACE]</span> Play
|
||||||
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button onClick={pause}>Pause</button>
|
<button className="p-2 bg-blue-200 w-full" onClick={pause}>
|
||||||
|
<span className="text-xs">[SPACE]</span> Pause
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
{selectedTime && (
|
{selectedTime && (
|
||||||
<button onClick={playSelection}>Play Selection</button>
|
<button className="p-2 bg-blue-200 w-full" onClick={playSelection}>
|
||||||
|
<span className="text-xs">[,]</span>Play Selection
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
{topicTime && <button onClick={playTopic}>Play Topic</button>}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,44 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"spin-slow": "spin 3s linear infinite",
|
"spin-slow": "spin 3s linear infinite",
|
||||||
|
"wave-quiet": "wave-quiet 1.2s ease-in-out infinite",
|
||||||
|
"wave-normal": "wave-normal 1.2s ease-in-out infinite",
|
||||||
|
"wave-loud": "wave-loud 1.2s ease-in-out infinite",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"wave-quiet": {
|
||||||
|
"25%": {
|
||||||
|
transform: "scaleY(.6)",
|
||||||
|
},
|
||||||
|
"50%": {
|
||||||
|
transform: "scaleY(.4)",
|
||||||
|
},
|
||||||
|
"75%": {
|
||||||
|
transform: "scaleY(.4)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"wave-normal": {
|
||||||
|
"25%": {
|
||||||
|
transform: "scaleY(1)",
|
||||||
|
},
|
||||||
|
"50%": {
|
||||||
|
transform: "scaleY(.4)",
|
||||||
|
},
|
||||||
|
"75%": {
|
||||||
|
transform: "scaleY(.6)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"wave-loud": {
|
||||||
|
"25%": {
|
||||||
|
transform: "scaleY(1)",
|
||||||
|
},
|
||||||
|
"50%": {
|
||||||
|
transform: "scaleY(.4)",
|
||||||
|
},
|
||||||
|
"75%": {
|
||||||
|
transform: "scaleY(1.2)",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
bluegrey: "RGB(90, 122, 158)",
|
bluegrey: "RGB(90, 122, 158)",
|
||||||
|
|||||||
Reference in New Issue
Block a user