separate out components

This commit is contained in:
talksik
2022-05-29 13:38:17 -05:00
parent 84208b28e0
commit 3f739ef69f
5 changed files with 427 additions and 399 deletions
@@ -0,0 +1,232 @@
/* eslint-disable jsx-a11y/media-has-caption */
import { blueGrey } from '@mui/material/colors';
import React, { useState, useEffect, useMemo } from 'react';
import { joinConversation, leaveConversation } from '../firebase/firestore';
import useAuth from '../providers/AuthProvider';
import { useImmer } from 'use-immer';
import useTerminal from './Terminal';
import { User } from '@nirvana/core/src/models/user.model';
import {
Stack,
IconButton,
Typography,
AvatarGroup,
Avatar,
Box,
Container,
Fab,
Paper,
} from '@mui/material';
import { FiSun, FiMoreVertical, FiPlay, FiCloudRain } from 'react-icons/fi';
export default function ConversationDetails() {
const { user } = useAuth();
const { getUser, selectedConversation, selectConversation, isCloudDoingMagic } = useTerminal();
const [conversationUsers, setConversationUsers] = useImmer<User[]>([]);
// TODO: at the terminal level, make sure we are listening for audio clips
// and here it's just an access of that map of content for each conversation's blocks
// the reference last active date based on current session of viewing this conversation
const [lastActiveDate, setLastActiveDate] = useState<Date>(null);
// join the room
useEffect(() => {
if (selectedConversation.membersInRoom?.length > 1) {
// join the audio room with hms
}
}, [selectedConversation.membersInRoom]);
// put me in the membersInRoom and leave every time I change conversations
useEffect(() => {
joinConversation(selectedConversation.id, user.uid);
const handleCloseWindow = (event: BeforeUnloadEvent) => {
leaveConversation(selectedConversation.id, user.uid);
};
window.addEventListener('beforeunload', handleCloseWindow);
return () => {
leaveConversation(selectedConversation.id, user.uid);
window.removeEventListener('beforeunload', handleCloseWindow);
};
}, [selectedConversation.id, user.uid]);
// update my last active date in this conversation for this viewing session's ui
useEffect(() => {
setLastActiveDate((prevLastActiveForCurrentSession) => {
if (prevLastActiveForCurrentSession) return prevLastActiveForCurrentSession;
return selectedConversation.members[user.uid].lastActiveDate?.toDate();
});
}, [selectedConversation.members, user.uid, setLastActiveDate]);
// TODO: uncheck priority or not
// soft max of 10...not going to enforce by counting all in database, but relying on client side list of convos
useEffect(() => {
(async () => {
const userPromises = selectedConversation.memberIdsList
// .filter((memberId) => memberId !== user.uid)
.map(async (memberId) => await getUser(memberId));
const userSettled = await Promise.all(userPromises);
setConversationUsers(userSettled);
})();
return;
}, [selectedConversation.memberIdsList, getUser, user, setConversationUsers]);
return (
<>
<Stack
direction={'row'}
sx={{
py: 2,
px: 2,
borderBottom: '1px solid',
borderBottomColor: blueGrey[100],
WebkitAppRegion: 'drag',
cursor: 'pointer',
}}
alignItems={'center'}
justifyContent={'flex-start'}
>
<IconButton color="primary" size="small">
<FiSun />
</IconButton>
<Typography sx={{ color: 'GrayText' }} variant={'overline'}>
{selectedConversation.name ??
conversationUsers.map((conversationUser) => conversationUser.displayName).join(', ')}
</Typography>
<AvatarGroup variant={'rounded'} sx={{ ml: 'auto' }}>
{conversationUsers.map((conversationUser, index) => (
<Avatar
key={`${selectedConversation.id}-${conversationUser.uid}-convoIcon`}
alt={conversationUser?.displayName}
src={conversationUser?.photoUrl}
sx={{
width: 30,
height: 30,
opacity: selectedConversation.membersInRoom?.includes(conversationUser.uid)
? '100%'
: '20%',
}}
/>
))}
</AvatarGroup>
<Box>
<IconButton size="small">
<FiMoreVertical />
</IconButton>
</Box>
</Stack>
<Container maxWidth={false} sx={{ position: 'relative', flex: 1 }}>
<ConversationHistory />
<Box
sx={{
position: 'absolute',
zIndex: 10,
bottom: 0,
right: 0,
padding: 3,
}}
>
<Fab color="primary" aria-label="add" size="medium">
<FiSun />
</Fab>
</Box>
</Container>
</>
);
}
function ConversationHistory() {
const { isCloudDoingMagic, selectedConversation, conversationContentMap } = useTerminal();
const contentBlocks = useMemo(() => {
return conversationContentMap[selectedConversation.id] ?? [];
}, [conversationContentMap, selectedConversation.id]);
return (
<Container maxWidth="xs">
<Stack
justifyContent={'flex-start'}
alignItems={'center'}
sx={{
pt: 2,
}}
>
<Typography variant="caption">yesterday</Typography>
<Paper elevation={1} sx={{ p: 1, width: '100%' }}>
<Stack direction={'row'} alignItems="center">
<Stack spacing={2} direction={'row'} alignItems={'center'}>
<Avatar alt={'Arjun Patel'} src="https://mui.com/static/images/avatar/2.jpg" />
<Typography color={'GrayText'} variant="overline">
{'Viet Phan'}
</Typography>
</Stack>
<Box
sx={{
ml: 'auto',
color: 'GrayText',
}}
>
<FiPlay />
</Box>
</Stack>
</Paper>
</Stack>
<Stack
justifyContent={'flex-start'}
alignItems={'center'}
sx={{
pt: 2,
}}
>
<Typography variant="caption">today</Typography>
{contentBlocks.map((contentBlock) => (
<Paper key={contentBlock.id} elevation={8} sx={{ p: 1, width: '100%' }}>
<Stack direction={'row'} alignItems="center">
{/* <Stack spacing={2} direction={'row'} alignItems={'center'}>
<Avatar alt={'Arjun Patel'} src="https://mui.com/static/images/avatar/2.jpg" />
<Typography color={'GrayText'} variant="overline">
{'Viet Phan'}
</Typography>
</Stack> */}
<audio controls src={contentBlock.contentUrl} />
<Box
sx={{
ml: 'auto',
color: 'GrayText',
}}
>
<FiPlay />
</Box>
</Stack>
</Paper>
))}
{isCloudDoingMagic && (
<IconButton size="small" color="secondary">
<FiCloudRain />
</IconButton>
)}
</Stack>
</Container>
);
}
@@ -0,0 +1,149 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
Avatar,
Badge,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
ListSubheader,
Stack,
Typography,
Input,
CircularProgress,
IconButton,
Box,
Tooltip,
ListItemSecondaryAction,
AvatarGroup,
} from '@mui/material';
import { blueGrey } from '@mui/material/colors';
import { FiActivity, FiInbox, FiSearch, FiUsers, FiCoffee, FiCircle } from 'react-icons/fi';
import NirvanaAvatar from './NirvanaAvatar';
import { useDebounce, useKey } from 'react-use';
import { useSnackbar } from 'notistack';
import KeyboardShortcutLabel from './KeyboardShortcutLabel';
import { searchUsers } from '../firebase/firestore';
import { User } from '@nirvana/core/src/models/user.model';
import useTerminal from './Terminal';
import Conversation from '@nirvana/core/src/models/conversation.model';
import useAuth from '../providers/AuthProvider';
import { useImmer } from 'use-immer';
/**
*
* @param lookingForSomeone: if a conversation id has been selected and we are looking for it
* @returns
*/
export function ConversationList({ lookingForSomeone = false }: { lookingForSomeone: boolean }) {
const { conversationMap } = useTerminal();
if (Object.keys(conversationMap).length === 0) {
return (
<Typography align="center" variant="caption">
Look for someone by name or email to start a conversation!
</Typography>
);
}
return (
<>
<List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<FiActivity />
<Typography variant="subtitle2"> Priority</Typography>
</ListSubheader>
}
>
{lookingForSomeone && <CircularProgress />}
{Object.values(conversationMap).map((currentConversation) => (
<ConversationRow
key={`${currentConversation.id}-priorityConvoList`}
conversation={currentConversation}
/>
))}
</List>
<Divider />
{/* <List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<FiInbox />
<Typography variant="subtitle2"> Inbox</Typography>
</ListSubheader>
}
></List> */}
</>
);
}
function ConversationRow({ conversation }: { conversation: Conversation }) {
const { user } = useAuth();
const { getUser, selectedConversation, selectConversation } = useTerminal();
const [conversationUsers, setConversationUsers] = useImmer<User[]>([]);
useEffect(() => {
(async () => {
const userPromises = conversation.memberIdsList
// .filter((memberId) => memberId !== user.uid)
.map(async (memberId) => await getUser(memberId));
const userSettled = await Promise.all(userPromises);
setConversationUsers(userSettled);
})();
return;
}, [conversation.memberIdsList, getUser, user, setConversationUsers]);
return (
<ListItem key={`${conversation.id}-priorityConvoList`}>
<ListItemButton
selected={selectedConversation?.id === conversation.id}
onClick={() => selectConversation(conversation.id)}
>
<Box sx={{ color: 'GrayText', fontSize: 10, mr: 2 }}>
<FiCircle />
</Box>
<Typography sx={{ mr: 'auto', color: 'GrayText' }} variant={'overline'}>
{conversationUsers.map((conversationUser) => conversationUser.displayName).join(', ')}
</Typography>
<ListItemAvatar>
<AvatarGroup variant={'rounded'}>
{conversationUsers.map((conversationUser, index) => (
<Avatar
key={`${conversation.id}-${conversationUser.uid}-convoIcon`}
alt={conversationUser?.displayName}
src={conversationUser?.photoUrl}
sx={{
width: 30,
height: 30,
opacity: conversation.membersInRoom?.includes(conversationUser.uid)
? '100%'
: '20%',
}}
/>
))}
</AvatarGroup>
</ListItemAvatar>
{/* <Typography variant={'caption'}>20 sec</Typography> */}
</ListItemButton>
</ListItem>
);
}
@@ -1,39 +0,0 @@
import React, { useCallback, useRef, useState } from 'react';
import {
Avatar,
Badge,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
ListSubheader,
Stack,
Typography,
Input,
CircularProgress,
IconButton,
Box,
Tooltip,
ListItemSecondaryAction,
} from '@mui/material';
import { blueGrey } from '@mui/material/colors';
import { FiActivity, FiInbox, FiSearch, FiUsers, FiCoffee } from 'react-icons/fi';
import NirvanaAvatar from './NirvanaAvatar';
import { useDebounce, useKey } from 'react-use';
import { useSnackbar } from 'notistack';
import KeyboardShortcutLabel from './KeyboardShortcutLabel';
import { searchUsers } from '../firebase/firestore';
import { User } from '@nirvana/core/src/models/user.model';
import useTerminal from './Terminal';
// const Conversations = () => {
// return (
// );
// };
// export default Conversations;
@@ -0,0 +1,43 @@
import { Grid, Container, Typography } from '@mui/material';
import { blueGrey } from '@mui/material/colors';
import React from 'react';
import useAuth from '../providers/AuthProvider';
import ConversationDetails from './ConversationDetails';
import useTerminal from './Terminal';
export default function MainPanel() {
const { user } = useAuth();
const { selectedConversation } = useTerminal();
// if selected conversation, show details
// do all fetching necessary to paint things here
// join the call so to speak
// show who is
return (
<Grid item xs={8} sx={{ backgroundColor: 'white', display: 'flex', flexDirection: 'column' }}>
{selectedConversation ? (
<ConversationDetails />
) : (
<Container
maxWidth={false}
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: 2,
flex: 1,
background: blueGrey[50],
}}
>
<Typography variant="h6">{`Hi ${
user.displayName?.split(' ')[0] ?? 'there'
}!`}</Typography>
<Typography variant="caption">Take a deep breath in.</Typography>
</Container>
)}
</Grid>
);
}
+3 -360
View File
@@ -79,6 +79,8 @@ import Channels from '../electron/constants';
import { uploadAudioClip } from '../firebase/firebaseStorage';
import { ContentBlock, ContentType } from '@nirvana/core/src/models/content.model';
import Navbar from './Navbar';
import MainPanel from './MainPanel';
import { ConversationList } from './ConversationList';
type ConversationMap = {
[conversationId: string]: Conversation;
};
@@ -562,7 +564,7 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
{searchVal ? (
<ListPeople people={searchUsersResults} />
) : (
<ListConversations
<ConversationList
lookingForSomeone={selectedConversationId && !selectedConversation}
/>
)}
@@ -748,363 +750,4 @@ function ListPeople({ people }: { people: User[] }) {
);
}
function ListConversations({ lookingForSomeone = false }: { lookingForSomeone: boolean }) {
const { conversationMap } = useTerminal();
if (Object.keys(conversationMap).length === 0) {
return (
<Typography align="center" variant="caption">
Look for someone by name or email to start a conversation!
</Typography>
);
}
return (
<>
<List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<FiActivity />
<Typography variant="subtitle2"> Priority</Typography>
</ListSubheader>
}
>
{lookingForSomeone && <CircularProgress />}
{Object.values(conversationMap).map((currentConversation) => (
<ConversationRow
key={`${currentConversation.id}-priorityConvoList`}
conversation={currentConversation}
/>
))}
</List>
<Divider />
{/* <List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<FiInbox />
<Typography variant="subtitle2"> Inbox</Typography>
</ListSubheader>
}
></List> */}
</>
);
}
function ConversationRow({ conversation }: { conversation: Conversation }) {
const { user } = useAuth();
const { getUser, selectedConversation, selectConversation } = useTerminal();
const [conversationUsers, setConversationUsers] = useImmer<User[]>([]);
useEffect(() => {
(async () => {
const userPromises = conversation.memberIdsList
// .filter((memberId) => memberId !== user.uid)
.map(async (memberId) => await getUser(memberId));
const userSettled = await Promise.all(userPromises);
setConversationUsers(userSettled);
})();
return;
}, [conversation.memberIdsList, getUser, user, setConversationUsers]);
return (
<ListItem key={`${conversation.id}-priorityConvoList`}>
<ListItemButton
selected={selectedConversation?.id === conversation.id}
onClick={() => selectConversation(conversation.id)}
>
<Box sx={{ color: 'GrayText', fontSize: 10, mr: 2 }}>
<FiCircle />
</Box>
<Typography sx={{ mr: 'auto', color: 'GrayText' }} variant={'overline'}>
{conversationUsers.map((conversationUser) => conversationUser.displayName).join(', ')}
</Typography>
<ListItemAvatar>
<AvatarGroup variant={'rounded'}>
{conversationUsers.map((conversationUser, index) => (
<Avatar
key={`${conversation.id}-${conversationUser.uid}-convoIcon`}
alt={conversationUser?.displayName}
src={conversationUser?.photoUrl}
sx={{
width: 30,
height: 30,
opacity: conversation.membersInRoom?.includes(conversationUser.uid)
? '100%'
: '20%',
}}
/>
))}
</AvatarGroup>
</ListItemAvatar>
{/* <Typography variant={'caption'}>20 sec</Typography> */}
</ListItemButton>
</ListItem>
);
}
function MainPanel() {
const { user } = useAuth();
const { selectedConversation } = useTerminal();
// if selected conversation, show details
// do all fetching necessary to paint things here
// join the call so to speak
// show who is
return (
<Grid item xs={8} sx={{ backgroundColor: 'white', display: 'flex', flexDirection: 'column' }}>
{selectedConversation ? (
<ConversationDetails />
) : (
<Container
maxWidth={false}
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: 2,
flex: 1,
background: blueGrey[50],
}}
>
<Typography variant="h6">{`Hi ${
user.displayName?.split(' ')[0] ?? 'there'
}!`}</Typography>
<Typography variant="caption">Take a deep breath in.</Typography>
</Container>
)}
</Grid>
);
}
const maxPriorityConvos = 5;
function ConversationDetails() {
const { user } = useAuth();
const { getUser, selectedConversation, selectConversation, isCloudDoingMagic } = useTerminal();
const [conversationUsers, setConversationUsers] = useImmer<User[]>([]);
// TODO: at the terminal level, make sure we are listening for audio clips
// and here it's just an access of that map of content for each conversation's blocks
// the reference last active date based on current session of viewing this conversation
const [lastActiveDate, setLastActiveDate] = useState<Date>(null);
// join the room
useEffect(() => {
if (selectedConversation.membersInRoom?.length > 1) {
// join the audio room with hms
}
}, [selectedConversation.membersInRoom]);
// put me in the membersInRoom and leave every time I change conversations
useEffect(() => {
joinConversation(selectedConversation.id, user.uid);
const handleCloseWindow = (event: BeforeUnloadEvent) => {
leaveConversation(selectedConversation.id, user.uid);
};
window.addEventListener('beforeunload', handleCloseWindow);
return () => {
leaveConversation(selectedConversation.id, user.uid);
window.removeEventListener('beforeunload', handleCloseWindow);
};
}, [selectedConversation.id, user.uid]);
// update my last active date in this conversation for this viewing session's ui
useEffect(() => {
setLastActiveDate((prevLastActiveForCurrentSession) => {
if (prevLastActiveForCurrentSession) return prevLastActiveForCurrentSession;
return selectedConversation.members[user.uid].lastActiveDate?.toDate();
});
}, [selectedConversation.members, user.uid, setLastActiveDate]);
// TODO: uncheck priority or not
// soft max of 10...not going to enforce by counting all in database, but relying on client side list of convos
useEffect(() => {
(async () => {
const userPromises = selectedConversation.memberIdsList
// .filter((memberId) => memberId !== user.uid)
.map(async (memberId) => await getUser(memberId));
const userSettled = await Promise.all(userPromises);
setConversationUsers(userSettled);
})();
return;
}, [selectedConversation.memberIdsList, getUser, user, setConversationUsers]);
return (
<>
<Stack
direction={'row'}
sx={{
py: 2,
px: 2,
borderBottom: '1px solid',
borderBottomColor: blueGrey[100],
WebkitAppRegion: 'drag',
cursor: 'pointer',
}}
alignItems={'center'}
justifyContent={'flex-start'}
>
<IconButton color="primary" size="small">
<FiSun />
</IconButton>
<Typography sx={{ color: 'GrayText' }} variant={'overline'}>
{selectedConversation.name ??
conversationUsers.map((conversationUser) => conversationUser.displayName).join(', ')}
</Typography>
<AvatarGroup variant={'rounded'} sx={{ ml: 'auto' }}>
{conversationUsers.map((conversationUser, index) => (
<Avatar
key={`${selectedConversation.id}-${conversationUser.uid}-convoIcon`}
alt={conversationUser?.displayName}
src={conversationUser?.photoUrl}
sx={{
width: 30,
height: 30,
opacity: selectedConversation.membersInRoom?.includes(conversationUser.uid)
? '100%'
: '20%',
}}
/>
))}
</AvatarGroup>
<Box>
<IconButton size="small">
<FiMoreVertical />
</IconButton>
</Box>
</Stack>
<Container maxWidth={false} sx={{ position: 'relative', flex: 1 }}>
<ConversationHistory />
<Box
sx={{
position: 'absolute',
zIndex: 10,
bottom: 0,
right: 0,
padding: 3,
}}
>
<Fab color="primary" aria-label="add" size="medium">
<FiSun />
</Fab>
</Box>
</Container>
</>
);
}
function ConversationHistory() {
const { isCloudDoingMagic, selectedConversation, conversationContentMap } = useTerminal();
const contentBlocks = useMemo(() => {
return conversationContentMap[selectedConversation.id] ?? [];
}, [conversationContentMap, selectedConversation.id]);
return (
<Container maxWidth="xs">
<Stack
justifyContent={'flex-start'}
alignItems={'center'}
sx={{
pt: 2,
}}
>
<Typography variant="caption">yesterday</Typography>
<Paper elevation={1} sx={{ p: 1, width: '100%' }}>
<Stack direction={'row'} alignItems="center">
<Stack spacing={2} direction={'row'} alignItems={'center'}>
<Avatar alt={'Arjun Patel'} src="https://mui.com/static/images/avatar/2.jpg" />
<Typography color={'GrayText'} variant="overline">
{'Viet Phan'}
</Typography>
</Stack>
<Box
sx={{
ml: 'auto',
color: 'GrayText',
}}
>
<FiPlay />
</Box>
</Stack>
</Paper>
</Stack>
<Stack
justifyContent={'flex-start'}
alignItems={'center'}
sx={{
pt: 2,
}}
>
<Typography variant="caption">today</Typography>
{contentBlocks.map((contentBlock) => (
<Paper key={contentBlock.id} elevation={8} sx={{ p: 1, width: '100%' }}>
<Stack direction={'row'} alignItems="center">
{/* <Stack spacing={2} direction={'row'} alignItems={'center'}>
<Avatar alt={'Arjun Patel'} src="https://mui.com/static/images/avatar/2.jpg" />
<Typography color={'GrayText'} variant="overline">
{'Viet Phan'}
</Typography>
</Stack> */}
<audio controls src={contentBlock.contentUrl} />
<Box
sx={{
ml: 'auto',
color: 'GrayText',
}}
>
<FiPlay />
</Box>
</Stack>
</Paper>
))}
{isCloudDoingMagic && (
<IconButton size="small" color="secondary">
<FiCloudRain />
</IconButton>
)}
</Stack>
</Container>
);
}