getting a basic queue but need to add whole timeline nicely
This commit is contained in:
@@ -2,16 +2,20 @@ import { useEffect, useState } from "react";
|
||||
import { configure, GlobalHotKeys, KeyMap } from "react-hotkeys";
|
||||
import { useRecoilState, useRecoilValue } from "recoil";
|
||||
import {
|
||||
allRelevantConversationsAtom,
|
||||
audioQueueAtom,
|
||||
hasMicPermissionsAtom,
|
||||
isRecordingAtom,
|
||||
priorityConvosSelector,
|
||||
selectedPriorityConvoAtom,
|
||||
selectedConvoAtom,
|
||||
} from "../../recoil/main";
|
||||
import MicRecorder from "mic-recorder-to-mp3";
|
||||
import toast from "react-hot-toast";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { conversationService } from "@nirvana/common/services";
|
||||
import { useAuth } from "../../contexts/authContext";
|
||||
import { useRouter } from "next/router";
|
||||
import { AudioClip } from "@nirvana/common/models/conversation";
|
||||
|
||||
// New instance
|
||||
const recorder = new MicRecorder({
|
||||
@@ -22,72 +26,48 @@ const MAX_SHORTCUT_MAPPINGS_PRIORITY = 8;
|
||||
|
||||
export default function AudioHandler() {
|
||||
const { currUser } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
const { convoId } = router.query;
|
||||
|
||||
const priorityConvos = useRecoilValue(priorityConvosSelector);
|
||||
const mapAllConvos = useRecoilValue(allRelevantConversationsAtom);
|
||||
|
||||
const [mapShortcutsToConvoId, setmapShortcutsToConvoId] = useState<
|
||||
Map<string, string>
|
||||
>(new Map<string, string>());
|
||||
|
||||
const [selectedConvoId, setSelectedConversationId] = useRecoilState(
|
||||
selectedPriorityConvoAtom
|
||||
);
|
||||
const [selectedConvoId, setSelectedConversationId] =
|
||||
useRecoilState(selectedConvoAtom);
|
||||
|
||||
const [audioQueue, setAudioQueue] = useRecoilState(audioQueueAtom);
|
||||
|
||||
function onEndedPlaying(e) {
|
||||
toast.success("finished playing");
|
||||
|
||||
// remove from queue and the queue manager will handle the rest
|
||||
setAudioQueue((prevQueue) => {
|
||||
const newQueue: AudioClip[] = [...prevQueue];
|
||||
newQueue.shift();
|
||||
return newQueue;
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (audioQueue.length > 0) {
|
||||
const audio = new Audio(audioQueue[0].audioDataUrl);
|
||||
audio.onended = onEndedPlaying;
|
||||
audio.play();
|
||||
}
|
||||
}, [audioQueue]);
|
||||
|
||||
const [hasMicPermissions, sethasMicPermissions] = useRecoilState(
|
||||
hasMicPermissionsAtom
|
||||
);
|
||||
|
||||
// set keyboard shortcuts for 1->8, but only if this component is shown, and not in stuff like
|
||||
// convo view
|
||||
useEffect(() => {
|
||||
// reset entire map of shortcuts now with new list of priority convos
|
||||
|
||||
const newMap = new Map<string, string>();
|
||||
|
||||
priorityConvos.map((convo, i) => {
|
||||
if (i < MAX_SHORTCUT_MAPPINGS_PRIORITY) {
|
||||
const shortcut = (i + 1).toString();
|
||||
newMap.set(shortcut, convo.id);
|
||||
}
|
||||
});
|
||||
setmapShortcutsToConvoId(newMap);
|
||||
}, [priorityConvos]);
|
||||
|
||||
const selectingPriorityConvoHandler = (event) => {
|
||||
const key = event.key;
|
||||
|
||||
// select the convo based on the right convo
|
||||
|
||||
if (!mapShortcutsToConvoId.has(key)) {
|
||||
toast.error("Not a valid conversation selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedConvoId = mapShortcutsToConvoId.get(key);
|
||||
|
||||
if (selectedConvoId) {
|
||||
setSelectedConversationId(selectedConvoId);
|
||||
}
|
||||
};
|
||||
|
||||
// set keyboard shortcuts for 1->8, but only if this component is shown, and not in stuff like
|
||||
// convo view
|
||||
useEffect(() => {
|
||||
// reset entire map of shortcuts now with new list of priority convos
|
||||
|
||||
const newMap = new Map<string, string>();
|
||||
|
||||
priorityConvos.map((convo, i) => {
|
||||
if (i < MAX_SHORTCUT_MAPPINGS_PRIORITY) {
|
||||
const shortcut = (i + 1).toString();
|
||||
newMap.set(shortcut, convo.id);
|
||||
}
|
||||
});
|
||||
setmapShortcutsToConvoId(newMap);
|
||||
}, [priorityConvos]);
|
||||
|
||||
// checking for permissions for recording
|
||||
useEffect(() => {
|
||||
try {
|
||||
// checking for permissions for recording
|
||||
// won't work in https!!!
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
@@ -111,6 +91,22 @@ export default function AudioHandler() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// set keyboard shortcuts for 1->8, but only if this component is shown, and not in stuff like
|
||||
// convo view
|
||||
useEffect(() => {
|
||||
// reset entire map of shortcuts now with new list of priority convos
|
||||
|
||||
const newMap = new Map<string, string>();
|
||||
|
||||
priorityConvos.map((convo, i) => {
|
||||
if (i < MAX_SHORTCUT_MAPPINGS_PRIORITY) {
|
||||
const shortcut = (i + 1).toString();
|
||||
newMap.set(shortcut, convo.id);
|
||||
}
|
||||
});
|
||||
setmapShortcutsToConvoId(newMap);
|
||||
}, [priorityConvos]);
|
||||
|
||||
configure({
|
||||
ignoreTags: ["input", "select", "textarea"],
|
||||
});
|
||||
@@ -205,9 +201,7 @@ export default function AudioHandler() {
|
||||
return;
|
||||
}
|
||||
|
||||
const currConvo = priorityConvos.find(
|
||||
(convo) => convo.id == selectedConvoId
|
||||
);
|
||||
const currConvo = mapAllConvos.get(selectedConvoId);
|
||||
|
||||
if (!currConvo?.cachedAudioClip) {
|
||||
toast.error("nothing to play");
|
||||
@@ -218,6 +212,29 @@ export default function AudioHandler() {
|
||||
audio.play();
|
||||
};
|
||||
|
||||
const selectingPriorityConvoHandler = (event) => {
|
||||
const key = event.key;
|
||||
|
||||
// STOP if we are in a convo detail page, as we want to talk in this channel, if so
|
||||
if (convoId) {
|
||||
toast.error("Cannot select a priority convo from here!");
|
||||
return;
|
||||
}
|
||||
|
||||
// select the convo based on the shortcut
|
||||
|
||||
if (!mapShortcutsToConvoId.has(key)) {
|
||||
toast.error("Not a valid conversation selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedConvoId = mapShortcutsToConvoId.get(key);
|
||||
|
||||
if (selectedConvoId) {
|
||||
setSelectedConversationId(selectedConvoId);
|
||||
}
|
||||
};
|
||||
|
||||
const keyMap: KeyMap = {
|
||||
SELECT_PRIORITY_CONVO: ["1", "2", "3", "4", "5", "6", "7", "8"],
|
||||
START_RECORDING: {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Avatar, Tooltip } from "antd";
|
||||
import { useRecoilState, useRecoilValue } from "recoil";
|
||||
import {
|
||||
checkIncomingMessageSelector,
|
||||
selectedPriorityConvoAtom,
|
||||
selectedConvoAtom,
|
||||
} from "../../recoil/main";
|
||||
import Conversation from "../../../common/models/conversation";
|
||||
import { MasterAvatarGroupWithUserFetch } from "../UserDetails/MasterAvatarGroup";
|
||||
@@ -23,9 +23,7 @@ export default function PriorityConvoRow(props: {
|
||||
itemIndex: number;
|
||||
}) {
|
||||
// figure out if this is a selectedConvo
|
||||
const [selectedConvo, setSelectedConvo] = useRecoilState(
|
||||
selectedPriorityConvoAtom
|
||||
);
|
||||
const [selectedConvo, setSelectedConvo] = useRecoilState(selectedConvoAtom);
|
||||
|
||||
const isSelected = selectedConvo == props.conversation.id ? true : false;
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@ import { yesterday } from "@nirvana/common/helpers/dateTime";
|
||||
import { AudioClip } from "@nirvana/common/models/conversation";
|
||||
import moment from "moment";
|
||||
import toast from "react-hot-toast";
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { useRecoilState, useRecoilValue } from "recoil";
|
||||
import {
|
||||
allRelevantContactsAtom,
|
||||
allUsersConversationsAtom,
|
||||
audioQueueAtom,
|
||||
audioQueueCurrentClipSelector,
|
||||
selectedConvoAtom,
|
||||
} from "../../recoil/main";
|
||||
import { MasterAvatarGroupWithUserFetch } from "../UserDetails/MasterAvatarGroup";
|
||||
import { useState } from "react";
|
||||
@@ -21,6 +24,9 @@ export default function TimelineAudioClip(props: {
|
||||
const audioClipUser = relContactsMap.get(props.audioClip.senderUserId);
|
||||
const userConvoAssoc = userConvosMap.get(props.convoId);
|
||||
|
||||
const audioQueueCurrentClip = useRecoilValue(audioQueueCurrentClipSelector);
|
||||
const [audioQueue, setAudioQueue] = useRecoilState(audioQueueAtom);
|
||||
|
||||
const [audioDuration, setAudioDuration] = useState<number>();
|
||||
|
||||
const loadedMetadata = (data) => {
|
||||
@@ -33,11 +39,21 @@ export default function TimelineAudioClip(props: {
|
||||
// audio.onloadedmetadata = loadedMetadata;
|
||||
|
||||
const playAudioClip = () => {
|
||||
toast(`${audioClipUser?.firstName} speaking...`);
|
||||
|
||||
audio.play();
|
||||
setAudioQueue((prevQueue) => {
|
||||
return [...prevQueue, props.audioClip];
|
||||
});
|
||||
};
|
||||
|
||||
let customClasses = "bg-slate-100 border";
|
||||
if (audioQueueCurrentClip?.id == props.audioClip.id) {
|
||||
customClasses = "animate-pulse bg-orange-200";
|
||||
} else if (
|
||||
props.audioClip.createdDate <
|
||||
(userConvoAssoc?.lastInteractionDate || yesterday)
|
||||
) {
|
||||
customClasses = "bg-sky-100";
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
onClick={playAudioClip}
|
||||
@@ -47,12 +63,8 @@ export default function TimelineAudioClip(props: {
|
||||
${
|
||||
props.index % 2 == 1 &&
|
||||
"translate-y-[4.75rem] border-t-4 border-t-teal-600"
|
||||
} ${props.index % 2 == 0 && "border-b-4 border-b-teal-600"} ${
|
||||
props.audioClip.createdDate <
|
||||
(userConvoAssoc?.lastInteractionDate || yesterday)
|
||||
? "bg-slate-100 border"
|
||||
: "bg-sky-100 "
|
||||
}`}
|
||||
} ${props.index % 2 == 0 && "border-b-4 border-b-teal-600"}
|
||||
${customClasses}`}
|
||||
style={{
|
||||
minWidth: "max-content",
|
||||
width: `100px`,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { QueryRoutes } from "@nirvana/common/helpers/routes";
|
||||
import { useRouter } from "next/router";
|
||||
import { GlobalHotKeys, KeyMap, configure } from "react-hotkeys";
|
||||
import { selectedPriorityConvoAtom } from "../../recoil/main";
|
||||
import { selectedConvoAtom } from "../../recoil/main";
|
||||
import { useSetRecoilState } from "recoil";
|
||||
|
||||
export default function KeyboardShortcutHandler() {
|
||||
@@ -11,7 +11,7 @@ export default function KeyboardShortcutHandler() {
|
||||
ignoreTags: [],
|
||||
});
|
||||
|
||||
const setSelectedPriorityConvo = useSetRecoilState(selectedPriorityConvoAtom);
|
||||
const setSelectedPriorityConvo = useSetRecoilState(selectedConvoAtom);
|
||||
|
||||
const handleEscape = () => {
|
||||
console.log("woah");
|
||||
|
||||
@@ -18,9 +18,10 @@ import {
|
||||
} from "react-icons/fa";
|
||||
import LinkIcon from "../Drawer/LinkIcon";
|
||||
import UserAvatar, { UserAvatarSizes } from "../UserDetails/UserAvatar";
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { useRecoilValue, useSetRecoilState } from "recoil";
|
||||
import {
|
||||
allRelevantConversationsAtom,
|
||||
selectedConvoAtom,
|
||||
userConvoAssociationSelector,
|
||||
} from "../../recoil/main";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -73,62 +74,6 @@ const testDrawerItems: {
|
||||
},
|
||||
];
|
||||
|
||||
const testAudioClips: {
|
||||
senderName: string;
|
||||
relativeSentTime: string;
|
||||
duration: number;
|
||||
alreadyPlayed: boolean;
|
||||
isGap?: boolean;
|
||||
isLink?: boolean;
|
||||
}[] = [
|
||||
{
|
||||
senderName: "John",
|
||||
relativeSentTime: "yesterday",
|
||||
duration: 150,
|
||||
alreadyPlayed: true,
|
||||
},
|
||||
|
||||
{
|
||||
senderName: "Moha",
|
||||
relativeSentTime: "5 minutes ago",
|
||||
duration: 600,
|
||||
alreadyPlayed: false,
|
||||
isGap: true,
|
||||
},
|
||||
{
|
||||
senderName: "Alex",
|
||||
relativeSentTime: "7 hours ago",
|
||||
duration: 10,
|
||||
alreadyPlayed: false,
|
||||
},
|
||||
{
|
||||
senderName: "Ben",
|
||||
relativeSentTime: "2 hours ago",
|
||||
duration: 300,
|
||||
alreadyPlayed: false,
|
||||
},
|
||||
|
||||
{
|
||||
senderName: "Alex",
|
||||
relativeSentTime: "30 minutes ago",
|
||||
duration: 200,
|
||||
alreadyPlayed: false,
|
||||
},
|
||||
{
|
||||
senderName: "Moha",
|
||||
relativeSentTime: "8 minutes ago",
|
||||
duration: 600,
|
||||
alreadyPlayed: false,
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
senderName: "Moha",
|
||||
relativeSentTime: "5 minutes ago",
|
||||
duration: 600,
|
||||
alreadyPlayed: false,
|
||||
},
|
||||
];
|
||||
|
||||
const AUDIO_CLIP_FETCH_LIMIT = 50;
|
||||
|
||||
export default function ViewConvo(props: { conversationId: string }) {
|
||||
@@ -158,7 +103,10 @@ export default function ViewConvo(props: { conversationId: string }) {
|
||||
|
||||
const [audioClips, setAudioClips] = useState<AudioClip[]>([] as AudioClip[]);
|
||||
|
||||
const setSelectedConvoId = useSetRecoilState(selectedConvoAtom);
|
||||
|
||||
useEffect(() => {
|
||||
// should have this convo, otherwise unauthorized
|
||||
if (!convo || !convo.activeMembers.includes(currUser!.uid)) {
|
||||
toast.error("Not authorized for this conversation");
|
||||
|
||||
@@ -170,6 +118,9 @@ export default function ViewConvo(props: { conversationId: string }) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set the selected convo as this one as we are now on this page
|
||||
setSelectedConvoId(props.conversationId);
|
||||
|
||||
// subscribe to the data for the conversation such that we can render the timeline
|
||||
// get all audioClips for this conversation realtime listener so that when I speak it's also put on the timeline
|
||||
// any new message is seamlessly added to the timeline
|
||||
@@ -235,22 +186,21 @@ export default function ViewConvo(props: { conversationId: string }) {
|
||||
{convo?.name}
|
||||
</span>
|
||||
|
||||
<ConversationMemberStateIcon
|
||||
convoUserAssocState={userConvoAssoc?.state}
|
||||
/>
|
||||
|
||||
<span className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200 group-hover:visible invisible">
|
||||
<FaEdit className="ml-auto text-md text-slate-400" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className="flex flex-row items-center space-x-2 hover:cursor-pointer group">
|
||||
<MasterAvatarGroupWithUserFetch
|
||||
listOfUserIds={convo?.activeMembers}
|
||||
showCurrUser={true}
|
||||
maxUserCount={10}
|
||||
size={UserAvatarSizes.small}
|
||||
/>
|
||||
{convo?.activeMembers && (
|
||||
<MasterAvatarGroupWithUserFetch
|
||||
listOfUserIds={convo.activeMembers}
|
||||
showCurrUser={true}
|
||||
maxUserCount={10}
|
||||
size={UserAvatarSizes.small}
|
||||
/>
|
||||
)}
|
||||
|
||||
<span className="text-xs text-slate-300 hover:decoration-slate-400 group-hover:underline ">
|
||||
{convo?.activeMembers.length} members
|
||||
</span>
|
||||
@@ -258,6 +208,12 @@ export default function ViewConvo(props: { conversationId: string }) {
|
||||
</span>
|
||||
|
||||
<span className="flex flex-row items-center space-x-3">
|
||||
{userConvoAssoc?.state && (
|
||||
<ConversationMemberStateIcon
|
||||
convoUserAssocState={userConvoAssoc.state}
|
||||
/>
|
||||
)}
|
||||
|
||||
{userConvoAssoc?.state != ConversationMemberState.default && (
|
||||
<button
|
||||
onClick={() =>
|
||||
|
||||
@@ -92,6 +92,8 @@ export default function MasterAvatarGroup(props: {
|
||||
export function MasterAvatarGroupWithUserFetch(props: {
|
||||
listOfUserIds: string[];
|
||||
showCurrUser?: boolean;
|
||||
maxUserCount?: number;
|
||||
size?: UserAvatarSizes;
|
||||
}) {
|
||||
const relContactsMap = useRecoilValue(allRelevantContactsAtom);
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ export enum RecoilActions {
|
||||
|
||||
IS_RECORDING_ATOM = "IS_RECORDING_ATOM",
|
||||
HAS_MIC_PERMISSIONS = "HAS_MIC_PERMISSIONS",
|
||||
|
||||
AUDIO_QUEUE_ATOM = "AUDIO_QUEUE_ATOM",
|
||||
AUDIO_QUEUE_CURRENT_ITEM_SELECTOR = "AUDIO_QUEUE_CURRENT_ITEM_SELECTOR",
|
||||
}
|
||||
|
||||
export const nirvanaUserDataAtom = atom<NirvanaUser | null>({
|
||||
@@ -410,7 +413,7 @@ export const isRecordingAtom = atom<boolean>({
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const selectedPriorityConvoAtom = atom<string | null>({
|
||||
export const selectedConvoAtom = atom<string | null>({
|
||||
// convoId
|
||||
key: RecoilActions.SELECTED_CONVERSATION_ATOM,
|
||||
default: null,
|
||||
@@ -420,3 +423,21 @@ export const hasMicPermissionsAtom = atom<boolean>({
|
||||
key: RecoilActions.HAS_MIC_PERMISSIONS,
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const audioQueueAtom = atom<AudioClip[]>({
|
||||
key: RecoilActions.AUDIO_QUEUE_ATOM,
|
||||
default: [] as AudioClip[],
|
||||
});
|
||||
|
||||
export const audioQueueCurrentClipSelector = selector<AudioClip | null>({
|
||||
key: RecoilActions.AUDIO_QUEUE_CURRENT_ITEM_SELECTOR,
|
||||
get: ({ get }) => {
|
||||
const currentQueue = get(audioQueueAtom);
|
||||
|
||||
if (currentQueue[0]) {
|
||||
return currentQueue[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user