mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
more styling + auto scroll behavior
This commit is contained in:
@@ -12,38 +12,59 @@ export function Dashboard({
|
|||||||
stream,
|
stream,
|
||||||
}) {
|
}) {
|
||||||
const [openIndex, setOpenIndex] = useState(null);
|
const [openIndex, setOpenIndex] = useState(null);
|
||||||
|
const [autoscrollEnabled, setAutoscrollEnabled] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (autoscrollEnabled) scrollToBottom();
|
||||||
|
}, [topics.length]);
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
const topicsDiv = document.getElementById("topics-div");
|
||||||
|
topicsDiv.scrollTop = topicsDiv.scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScroll = (e) => {
|
||||||
|
const bottom = e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight;
|
||||||
|
if (!bottom && autoscrollEnabled) {
|
||||||
|
setAutoscrollEnabled(false);
|
||||||
|
} else if (bottom && !autoscrollEnabled) {
|
||||||
|
setAutoscrollEnabled(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="h-[60svh] w-3/4 flex flex-col">
|
<div className="relative h-[60svh] w-3/4 flex flex-col">
|
||||||
<div className="text-center py-6">
|
<div className="text-center pb-1 pt-4">
|
||||||
<h1 className="text-2xl font-bold text-blue-500">Meeting Notes</h1>
|
<h1 className="text-2xl font-bold text-blue-500">Meeting Notes</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between border-b-2">
|
<div className="flex justify-between border-b-2">
|
||||||
<div className="w-1/4">Timestamp</div>
|
<div className="w-1/4 font-bold">Timestamp</div>
|
||||||
<div className="w-1/4">Topic</div>
|
<div className="w-3/4 font-bold">Topic</div>
|
||||||
<div className="w-1/4"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-2 overflow-y-auto">
|
<div
|
||||||
|
className={`absolute top-7 right-5 w-10 h-10 ${autoscrollEnabled ? 'hidden' : 'flex'} justify-center items-center text-2xl cursor-pointer opacity-70 hover:opacity-100 transition-opacity duration-200 animate-bounce rounded-xl border-slate-400 bg-[#3c82f638] text-[#3c82f6ed]`}
|
||||||
|
onClick={scrollToBottom}
|
||||||
|
>⬇</div>
|
||||||
|
<div id="topics-div" className="py-2 overflow-y-auto" onScroll={handleScroll}>
|
||||||
{topics.map((item, index) => (
|
{topics.map((item, index) => (
|
||||||
<div key={index} className="border-b-2 py-2">
|
<div key={index} className="border-b-2 py-2 hover:bg-[#8ec5fc30]">
|
||||||
<div
|
<div
|
||||||
className="flex justify-between items-center cursor-pointer"
|
className="flex justify-between items-center cursor-pointer px-4"
|
||||||
onClick={() => setOpenIndex(openIndex === index ? null : index)}
|
onClick={() => setOpenIndex(openIndex === index ? null : index)}
|
||||||
>
|
>
|
||||||
<div className="w-1/4">{item.timestamp}</div>
|
<div className="w-1/4">{item.timestamp}</div>
|
||||||
<div className="w-1/4 flex justify-between items-center">
|
<div className="w-3/4 flex justify-between items-center">
|
||||||
{item.title}
|
{item.title}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
className={`transform transition-transform duration-200`}
|
className={`transform transition-transform duration-200`}
|
||||||
icon={openIndex === index ? faChevronDown : faChevronRight}
|
icon={openIndex === index ? faChevronDown : faChevronRight}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/4 flex flex-row space-x-0.5"></div>
|
|
||||||
</div>
|
</div>
|
||||||
{openIndex === index && (
|
{openIndex === index && (
|
||||||
<div className="mt-2 p-2 bg-white rounded">{item.transcript}</div>
|
<div className="p-2 mt-2 -mb-2 bg-slate-50 rounded">{item.transcript}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -53,7 +74,7 @@ export function Dashboard({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{finalSummary && (
|
{finalSummary && (
|
||||||
<div className="mt-2 p-2 bg-white temp-transcription rounded">
|
<div className="min-h-[200px] overflow-y-auto mt-2 p-2 bg-white temp-transcription rounded">
|
||||||
<h2>Final Summary</h2>
|
<h2>Final Summary</h2>
|
||||||
<p>Duration: {finalSummary.duration}</p>
|
<p>Duration: {finalSummary.duration}</p>
|
||||||
<p>{finalSummary.summary}</p>
|
<p>{finalSummary.summary}</p>
|
||||||
@@ -61,7 +82,7 @@ export function Dashboard({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer className="h-[7svh] w-full bg-gray-800 text-white text-center py-4 text-2xl mt-8 h-32">
|
<footer className="h-[7svh] w-full bg-gray-800 text-white text-center py-4 text-2xl">
|
||||||
{transcriptionText}
|
{transcriptionText}
|
||||||
</footer>
|
</footer>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function Recorder(props) {
|
|||||||
});
|
});
|
||||||
const wsWrapper = _wavesurfer.getWrapper();
|
const wsWrapper = _wavesurfer.getWrapper();
|
||||||
wsWrapper.style.cursor = "pointer";
|
wsWrapper.style.cursor = "pointer";
|
||||||
wsWrapper.style.backgroundColor = "lightgray";
|
wsWrapper.style.backgroundColor = "#e0c3fc42";
|
||||||
wsWrapper.style.borderRadius = "15px";
|
wsWrapper.style.borderRadius = "15px";
|
||||||
|
|
||||||
_wavesurfer.on("play", () => {
|
_wavesurfer.on("play", () => {
|
||||||
@@ -114,6 +114,7 @@ export default function Recorder(props) {
|
|||||||
<AudioInputsDropdown setDeviceId={setDeviceId} disabled={isRecording} />
|
<AudioInputsDropdown setDeviceId={setDeviceId} disabled={isRecording} />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
className="w-20"
|
||||||
onClick={handleRecClick}
|
onClick={handleRecClick}
|
||||||
data-color={isRecording ? "red" : "blue"}
|
data-color={isRecording ? "red" : "blue"}
|
||||||
disabled={!deviceId}
|
disabled={!deviceId}
|
||||||
@@ -122,6 +123,7 @@ export default function Recorder(props) {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
className="w-20"
|
||||||
id="play-btn"
|
id="play-btn"
|
||||||
onClick={handlePlayClick}
|
onClick={handlePlayClick}
|
||||||
data-color={isPlaying ? "orange" : "green"}
|
data-color={isPlaying ? "orange" : "green"}
|
||||||
@@ -129,8 +131,9 @@ export default function Recorder(props) {
|
|||||||
{isPlaying ? "Pause" : "Play"}
|
{isPlaying ? "Pause" : "Play"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div ref={waveformRef} className="w-full"></div>
|
<div ref={waveformRef} className="w-full shadow-xl rounded-2xl"></div>
|
||||||
{/* TODO: Download audio <a> tag */}
|
{/* TODO: Download audio <a> tag */}
|
||||||
|
{/* TODO: current time / audio duration */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,3 +18,10 @@ body {
|
|||||||
background: beige;
|
background: beige;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Dropdown-placeholder {
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.Dropdown-arrow {
|
||||||
|
top: 47% !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const App = () => {
|
|||||||
const sendStopCmd = () => serverData?.peer?.send(JSON.stringify({ cmd: "STOP" }))
|
const sendStopCmd = () => serverData?.peer?.send(JSON.stringify({ cmd: "STOP" }))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center h-[100svh]">
|
<div className="flex flex-col items-center h-[100svh] bg-gradient-to-r from-[#8ec5fc30] to-[#e0c3fc42]">
|
||||||
<div className="h-[13svh] flex flex-col justify-center items-center">
|
<div className="h-[13svh] flex flex-col justify-center items-center">
|
||||||
<h1 className="text-5xl font-bold text-blue-500">Reflector</h1>
|
<h1 className="text-5xl font-bold text-blue-500">Reflector</h1>
|
||||||
<p className="text-gray-500">Capture The Signal, Not The Noise</p>
|
<p className="text-gray-500">Capture The Signal, Not The Noise</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user