import { $desktopMode, $selectedLineId } from '../../controller/recoil'; import { Avatar, Skeleton, Tooltip } from 'antd'; import { FiActivity, FiHeadphones, FiSettings, FiSun } from 'react-icons/fi'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useGetUserDetails, useUserLines } from '../../controller/index'; import { useRecoilState, useSetRecoilState } from 'recoil'; import { FaPlus } from 'react-icons/fa'; import LineIcon from '../../components/lines/lineIcon/index'; import { LineMemberState } from '@nirvana/core/models/line.model'; import LineRow from '../../components/lines/lineRow.tsx/index'; import MasterLineData from '@nirvana/core/models/masterLineData.model'; import NewLineModal from './newLine'; import toast from 'react-hot-toast'; import { useLineDataProvider } from '../../controller/lineDataProvider'; /** * Socket Provider * Line Data Provider */ export default function NirvanaTerminal({ overlayOnly }: { overlayOnly: boolean }) { const [isModalVisible, setIsModalVisible] = useState(false); const { data: userDetails } = useGetUserDetails(); const [selectedLineId, setSelectedLineId] = useRecoilState($selectedLineId); const [desktopMode, setDesktopMode] = useRecoilState($desktopMode); // simply using this query for specific data on loading // todo: add these properties in context provider value although more work down the line for control const { isLoading: isLoadingInitialLines } = useUserLines(); const { linesMap, handleTuneToLine, handleUnTuneToLine } = useLineDataProvider(); useEffect(() => { // console.log("change/update in lines map"); // console.log(linesMap); }, [linesMap]); // ! remounting when toggle tuning and untoggle tuning a line // because it changes the list that it's in...solution 1: put everything in one list and use one sort to handle toggle tuned items to be clean // the change in the object or the property which is specifically currentUserMember of the masterLineData sent in to line row // causes a re-render but not a unmount and remount const allLines: MasterLineData[] = useMemo(() => { const masterLines: MasterLineData[] = Object.values(linesMap); // TODO: sort based on the audio blocks and currentMember lastActiveDate return masterLines.filter( (masterLine) => masterLine.currentUserMember.state === LineMemberState.INBOX, ); }, [linesMap]); const toggleTunedLines = useMemo(() => { const masterLines: MasterLineData[] = Object.values(linesMap); return masterLines.filter( (masterLine) => masterLine.currentUserMember.state === LineMemberState.TUNED, ); }, [linesMap]); const selectedLine: MasterLineData | undefined = useMemo(() => { if (!selectedLineId) return undefined; // find the line from the data provider if (linesMap[selectedLineId]) { // console.log("looking for selected Line in map for details section"); const foundSelectedLine = linesMap[selectedLineId]; // on mount of this, we want to temporarily tune into the line if we are not already tuned in...which would happen if we toggle tuned in if (!foundSelectedLine.tunedInMemberIds?.includes(userDetails?.user?._id.toString())) { handleTuneToLine(selectedLineId, false); } return { ...foundSelectedLine }; } return undefined; }, [selectedLineId, linesMap, userDetails]); // todo: sort/order based on activity and activity date and currently broadcasting/live const handleEscape = useCallback(() => { console.log('deselecting line'); setSelectedLineId((prevSelectedLineId) => { // ! only want to untune if it's a temporarily tuned line if (selectedLine?.currentUserMember?.state === LineMemberState.INBOX) handleUnTuneToLine(prevSelectedLineId); return null; }); }, [setSelectedLineId, selectedLine]); /** show user line details on click of one line */ const handleSelectLine = useCallback( (newLineIdToSelect: string) => { if (newLineIdToSelect !== selectedLine?.lineDetails._id.toString()) { setSelectedLineId((prevSelectedLineId) => { // untune myself from the previously selected if it was a temporary one/inbox // todo: p3: consolidate this logic as it's used in escape but different scenarios sort of so p3 if (prevSelectedLineId && selectedLine.currentUserMember?.state === LineMemberState.INBOX) handleUnTuneToLine(prevSelectedLineId); return newLineIdToSelect; }); } }, [setSelectedLineId, selectedLine], ); const handleToggleTuneToLine = useCallback( (lineId: string, turnToggleOn: boolean) => { // inhibit if they are trying to turn on and already have 3 toggle tuned if (toggleTunedLines?.length >= 3 && turnToggleOn) { toast.error('You cannot toggle more than 3 lines!'); return; } handleTuneToLine(lineId, turnToggleOn); }, [toggleTunedLines, handleTuneToLine], ); const handleStartBroadcast = useCallback( (lineId: string) => () => { console.log(`starting broadcast for ${lineId}!!!`); // todo: enable stream in this tuned in channel // if (lineId) handleUserBroadcast(lineId, true); }, [], ); const handleStopBroadcast = useCallback( (lineId: string) => () => { console.log(`stopping broadcast for ${lineId}!!!`); // todo: disable stream in this tuned in channel // if (lineId) handleUserBroadcast(lineId, false); }, [], ); const keyMap: KeyMap = useMemo( () => ({ DESELECT_LINE: 'esc', START_BROADCAST: { sequence: '`', action: 'keydown', }, STOP_BROADCAST: { sequence: '`', action: 'keyup', }, }), [], ); const handlers = useMemo( () => ({ DESELECT_LINE: handleEscape, START_BROADCAST: handleStartBroadcast(selectedLineId), STOP_BROADCAST: handleStopBroadcast(selectedLineId), }), [selectedLineId], ); return ( <>
{/* modal for creating new line */} setIsModalVisible(false)} /> {/* tuned in lines block */}
{/* tuned in header + general controls */}

Rooms

{`${toggleTunedLines?.length || 0}/3`}

{/* list of toggle tuned lines */}
{toggleTunedLines.map((masterLineData) => ( ))}
{!(allLines.length > 0) && !(toggleTunedLines.length > 0) && ( You have no lines!
Create one to connect to your team instantly.
)} {/* rest of the lines */}
{isLoadingInitialLines ? ( ) : ( allLines.map((masterLineData) => ( )) )}
setIsModalVisible(true)} className="absolute bottom-3 right-3 z-10 scale-0 group-hover:scale-100 ease-in-out hover:transition group-hover:transition delay-100 duration-200" >
{selectedLine ? ( ) : (
{`Hi ${userDetails?.user?.givenName}!`} You're all set!
)}
); } function LineDetailsTerminal({ selectedLine, handleToggleTuneToLine, }: { selectedLine: MasterLineData; handleToggleTuneToLine: (lineId: string, turnToggleOn: boolean) => void; }) { const { data: userDetails } = useGetUserDetails(); console.log('selected line', selectedLine); const isUserToggleTuned = useMemo( () => selectedLine?.currentUserMember?.state === LineMemberState.TUNED, [selectedLine], ); // seeing if I am in the list of broadcasters // the source of truth from the socket connections telling me if my clicking actually made a round trip const isUserBroadcasting = useMemo( () => selectedLine?.currentBroadcastersUserIds?.includes(userDetails?.user._id.toString()), [userDetails, selectedLine], ); // showing all tuned in members...they may not hear me since they might be doing something else // but feeling of presentness const tunedProfiles = useMemo(() => { const pictureSources: { name: string; pictureSrc: string }[] = []; selectedLine.tunedInMemberIds?.forEach((tunedInMemberUserId) => { // don't want to see my own picture // TODO: don't show myself!? if (tunedInMemberUserId === userDetails.user._id.toString()) { pictureSources.push(); pictureSources.push({ name: userDetails.user?.givenName, pictureSrc: userDetails.user?.picture, }); return; } const otherUserObject = selectedLine.otherUserObjects?.find( (userObj) => userObj._id.toString() === tunedInMemberUserId, ); if (otherUserObject?.picture) pictureSources.push({ name: otherUserObject.givenName, pictureSrc: otherUserObject.picture, }); }); return pictureSources; }, [selectedLine, userDetails]); // pics for the line icons const profilePictures = useMemo(() => { const pictureSources: string[] = []; // ?don't add in my image as that's useless contextually? // if (userData?.user?.picture) pictureSources.push(userData.user.picture); selectedLine.otherUserObjects?.forEach((otherUser) => { if (otherUser.picture) pictureSources.push(otherUser.picture); }); return pictureSources; }, [selectedLine, userDetails]); return (
{/* line details */}
{profilePictures && }

{selectedLine.lineDetails.name || selectedLine.otherUserObjects[0].givenName}

{`${ selectedLine.otherMembers?.length + 1 ?? 0 } members`} {`${ selectedLine.tunedInMemberIds?.length ?? 0 } in this room`}
{/* TODO: move to on hover of line row */}
{/* line timeline */}
load more yesterday
{selectedLine.otherUserObjects.map((otherUser) => (
{otherUser.givenName} {`${ Math.floor(Math.random() * 10) + 1 }:${Math.floor(Math.random() * 100) + 10}pm |`} {`${ Math.floor(Math.random() * 60) + 1 } seconds`}
))}
{'Arjun Patel'} {`${ Math.floor(Math.random() * 10) + 1 }:${Math.floor(Math.random() * 100) + 10}pm |`} {`${ Math.floor(Math.random() * 60) + 1 } seconds`}
today
{selectedLine.otherUserObjects.map((otherUser) => ( // TODO: show the shadow if it's unheard
{otherUser.name} {`${ Math.floor(Math.random() * 10) + 1 }:${Math.floor(Math.random() * 100) + 10}pm |`} {`${ Math.floor(Math.random() * 60) + 1 } seconds`}
))}
{/* live broadcasters */} right now
{selectedLine.otherUserObjects.map((otherUser) => (
{otherUser.name}
))}
); }