nice sort of display of priority convos and such
This commit is contained in:
@@ -64,6 +64,10 @@ function useSocketFire() {
|
|||||||
interface IConversationContext {
|
interface IConversationContext {
|
||||||
conversationMap: ConversationMap;
|
conversationMap: ConversationMap;
|
||||||
|
|
||||||
|
// sorted conversation lists
|
||||||
|
priorityConversations: MasterConversation[];
|
||||||
|
inboxConversations: MasterConversation[];
|
||||||
|
|
||||||
selectedConversation?: MasterConversation;
|
selectedConversation?: MasterConversation;
|
||||||
selectConversation?: (conversationId: string, temporaryOverrideSort?: boolean) => Promise<void>;
|
selectConversation?: (conversationId: string, temporaryOverrideSort?: boolean) => Promise<void>;
|
||||||
|
|
||||||
@@ -72,6 +76,8 @@ interface IConversationContext {
|
|||||||
|
|
||||||
const ConversationContext = React.createContext<IConversationContext>({
|
const ConversationContext = React.createContext<IConversationContext>({
|
||||||
conversationMap: {},
|
conversationMap: {},
|
||||||
|
priorityConversations: [],
|
||||||
|
inboxConversations: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ConversationProvider({ children }: { children: React.ReactNode }) {
|
export function ConversationProvider({ children }: { children: React.ReactNode }) {
|
||||||
@@ -339,6 +345,43 @@ export function ConversationProvider({ children }: { children: React.ReactNode }
|
|||||||
return tunedConversations;
|
return tunedConversations;
|
||||||
}, [conversationMap, user]);
|
}, [conversationMap, user]);
|
||||||
|
|
||||||
|
const masterConversations = useMemo(() => {
|
||||||
|
const priorityConversations: MasterConversation[] = [];
|
||||||
|
const inboxConversations: MasterConversation[] = [];
|
||||||
|
|
||||||
|
Object.values(conversationMap).forEach((currentMasterConversation) => {
|
||||||
|
const personalConvoMember = currentMasterConversation.members.find(
|
||||||
|
(mem) => mem._id.toString() === user._id.toString(),
|
||||||
|
);
|
||||||
|
if (personalConvoMember && personalConvoMember.memberState === 'priority') {
|
||||||
|
priorityConversations.push(currentMasterConversation);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inboxConversations.push(currentMasterConversation);
|
||||||
|
});
|
||||||
|
|
||||||
|
// sort each list
|
||||||
|
inboxConversations.sort((a, b) => {
|
||||||
|
if (a.temporaryOverrideSort) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.temporaryOverrideSort) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by which ones have new activity for me
|
||||||
|
// then sort by last activity date
|
||||||
|
});
|
||||||
|
|
||||||
|
priorityConversations.sort((a, b) => {
|
||||||
|
return b.createdDate.getTime() - a.createdDate.getTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
return [priorityConversations, inboxConversations];
|
||||||
|
}, [conversationMap, user]);
|
||||||
|
|
||||||
if (fetchState.error) {
|
if (fetchState.error) {
|
||||||
return (
|
return (
|
||||||
<Typography variant={'h6'} color={'danger'}>
|
<Typography variant={'h6'} color={'danger'}>
|
||||||
@@ -354,6 +397,8 @@ export function ConversationProvider({ children }: { children: React.ReactNode }
|
|||||||
handleStartConversation,
|
handleStartConversation,
|
||||||
selectedConversation,
|
selectedConversation,
|
||||||
selectConversation,
|
selectConversation,
|
||||||
|
priorityConversations: masterConversations[0],
|
||||||
|
inboxConversations: masterConversations[1],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function ConversationList() {
|
|||||||
|
|
||||||
const { omniSearch } = useSearch();
|
const { omniSearch } = useSearch();
|
||||||
|
|
||||||
const { conversationMap } = useConversations();
|
const { conversationMap, inboxConversations } = useConversations();
|
||||||
|
|
||||||
const rendersCount = useRendersCount();
|
const rendersCount = useRendersCount();
|
||||||
// console.warn('RENDER COUNT | CONVERSATION LIST | ', rendersCount);
|
// console.warn('RENDER COUNT | CONVERSATION LIST | ', rendersCount);
|
||||||
@@ -51,43 +51,6 @@ export function ConversationList() {
|
|||||||
omniSearch(SUPPORT_DISPLAY_NAME);
|
omniSearch(SUPPORT_DISPLAY_NAME);
|
||||||
}, [omniSearch]);
|
}, [omniSearch]);
|
||||||
|
|
||||||
const masterConversations = useMemo(() => {
|
|
||||||
const priorityConversations: MasterConversation[] = [];
|
|
||||||
const inboxConversations: MasterConversation[] = [];
|
|
||||||
|
|
||||||
Object.values(conversationMap).forEach((currentMasterConversation) => {
|
|
||||||
const personalConvoMember = currentMasterConversation.members.find(
|
|
||||||
(mem) => mem._id.toString() === user._id.toString(),
|
|
||||||
);
|
|
||||||
if (personalConvoMember && personalConvoMember.memberState === 'priority') {
|
|
||||||
priorityConversations.push(currentMasterConversation);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
inboxConversations.push(currentMasterConversation);
|
|
||||||
});
|
|
||||||
|
|
||||||
// sort each list
|
|
||||||
inboxConversations.sort((a, b) => {
|
|
||||||
if (a.temporaryOverrideSort) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b.temporaryOverrideSort) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by which ones have new activity for me
|
|
||||||
// then sort by last activity date
|
|
||||||
});
|
|
||||||
|
|
||||||
priorityConversations.sort((a, b) => {
|
|
||||||
return b.createdDate.getTime() - a.createdDate.getTime();
|
|
||||||
});
|
|
||||||
|
|
||||||
return [priorityConversations, inboxConversations];
|
|
||||||
}, [conversationMap, user]);
|
|
||||||
|
|
||||||
if (Object.keys(conversationMap).length === 0) {
|
if (Object.keys(conversationMap).length === 0) {
|
||||||
return (
|
return (
|
||||||
<Stack direction={'column'} alignItems="center">
|
<Stack direction={'column'} alignItems="center">
|
||||||
@@ -123,37 +86,6 @@ export function ConversationList() {
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<List
|
|
||||||
sx={{
|
|
||||||
pt: 2,
|
|
||||||
}}
|
|
||||||
subheader={
|
|
||||||
<ListSubheader>
|
|
||||||
<FiActivity />
|
|
||||||
<Typography variant="subtitle2"> Priority</Typography>
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{masterConversations[0].length === 0 && (
|
|
||||||
<Stack direction={'column'} alignItems="center">
|
|
||||||
<Typography align="center" variant="caption">
|
|
||||||
Mark conversations as priority to hear them across the hall.
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{masterConversations[0].map((currentConversation, index) => (
|
|
||||||
<ConversationRow
|
|
||||||
key={`${currentConversation._id.toString()}-priorityConvoList`}
|
|
||||||
conversation={currentConversation}
|
|
||||||
index={index}
|
|
||||||
isPriority={true}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<List
|
<List
|
||||||
sx={{
|
sx={{
|
||||||
pt: 2,
|
pt: 2,
|
||||||
@@ -165,7 +97,7 @@ export function ConversationList() {
|
|||||||
</ListSubheader>
|
</ListSubheader>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{masterConversations[1].map((currentConversation, index) => (
|
{inboxConversations.map((currentConversation, index) => (
|
||||||
<ConversationRow
|
<ConversationRow
|
||||||
key={`${currentConversation._id.toString()}-inboxConvoList`}
|
key={`${currentConversation._id.toString()}-inboxConvoList`}
|
||||||
conversation={currentConversation}
|
conversation={currentConversation}
|
||||||
|
|||||||
@@ -39,9 +39,10 @@ import {
|
|||||||
FiVideoOff,
|
FiVideoOff,
|
||||||
FiX,
|
FiX,
|
||||||
} from 'react-icons/fi';
|
} from 'react-icons/fi';
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import ConversationLabel from '../subcomponents/ConversationLabel';
|
import ConversationLabel from '../subcomponents/ConversationLabel';
|
||||||
|
import { MasterConversation } from '../util/types';
|
||||||
import { SUPPORT_DISPLAY_NAME } from '../util/support';
|
import { SUPPORT_DISPLAY_NAME } from '../util/support';
|
||||||
import { blueGrey } from '@mui/material/colors';
|
import { blueGrey } from '@mui/material/colors';
|
||||||
import useAuth from '../providers/AuthProvider';
|
import useAuth from '../providers/AuthProvider';
|
||||||
@@ -53,7 +54,7 @@ import useZen from '../providers/ZenProvider';
|
|||||||
export default function FooterControls() {
|
export default function FooterControls() {
|
||||||
const { handleFlowState } = useZen();
|
const { handleFlowState } = useZen();
|
||||||
|
|
||||||
const { selectedConversation } = useConversations();
|
const { selectedConversation, priorityConversations } = useConversations();
|
||||||
const { user, handleLogout } = useAuth();
|
const { user, handleLogout } = useAuth();
|
||||||
|
|
||||||
const [openUserSettings, setOpenUserSettings] = useState<boolean>(false);
|
const [openUserSettings, setOpenUserSettings] = useState<boolean>(false);
|
||||||
@@ -69,6 +70,18 @@ export default function FooterControls() {
|
|||||||
setOpenUserSettings(false);
|
setOpenUserSettings(false);
|
||||||
}, [setOpenUserSettings]);
|
}, [setOpenUserSettings]);
|
||||||
|
|
||||||
|
// is there a selected conversation and if so, is it a priority one?
|
||||||
|
const isSelectedConversationPriority = useMemo(() => {
|
||||||
|
const priorityIds =
|
||||||
|
priorityConversations.map((priorityConvo) => priorityConvo._id.toString()) ?? [];
|
||||||
|
|
||||||
|
if (selectedConversation && priorityIds.includes(selectedConversation._id.toString())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}, [selectedConversation, priorityConversations]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -90,93 +103,23 @@ export default function FooterControls() {
|
|||||||
p: 1,
|
p: 1,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
|
spacing={2}
|
||||||
>
|
>
|
||||||
{selectedConversation && (
|
{priorityConversations.map((masterPriorityConversation) => (
|
||||||
<Stack
|
<OverlayConversation
|
||||||
direction={'column'}
|
key={`priorityConversationOverlay-${masterPriorityConversation._id.toString()}`}
|
||||||
spacing={1}
|
masterConversation={masterPriorityConversation}
|
||||||
sx={{
|
isSelected={
|
||||||
p: 1,
|
masterPriorityConversation._id.toString() === selectedConversation?._id.toString()
|
||||||
borderRadius: 2,
|
}
|
||||||
bgcolor: blueGrey[300],
|
/>
|
||||||
}}
|
))}
|
||||||
alignItems={'center'}
|
|
||||||
>
|
|
||||||
{/* status */}
|
|
||||||
<Box
|
|
||||||
component={'span'}
|
|
||||||
sx={{
|
|
||||||
bgcolor: (theme) => theme.palette.secondary.main,
|
|
||||||
height: 5,
|
|
||||||
width: 5,
|
|
||||||
borderRadius: 100,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AvatarGroup
|
|
||||||
variant={'rounded'}
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
'& >:first-child': {
|
|
||||||
marginTop: 0,
|
|
||||||
},
|
|
||||||
'& > *': {
|
|
||||||
marginLeft: '0 !important' as any,
|
|
||||||
marginTop: -2,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedConversation.members?.map((conversationUser, index) => (
|
|
||||||
<Avatar
|
|
||||||
key={`${selectedConversation._id.toString()}-${conversationUser._id.toString()}-convoIcon`}
|
|
||||||
alt={conversationUser?.givenName}
|
|
||||||
src={conversationUser?.picture}
|
|
||||||
sx={{
|
|
||||||
opacity: selectedConversation.tunedInUsers?.includes(
|
|
||||||
conversationUser._id.toString(),
|
|
||||||
)
|
|
||||||
? '100%'
|
|
||||||
: '20%',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</AvatarGroup>
|
|
||||||
|
|
||||||
|
{selectedConversation && !isSelectedConversationPriority && (
|
||||||
|
<>
|
||||||
<Divider orientation="horizontal" flexItem />
|
<Divider orientation="horizontal" flexItem />
|
||||||
|
<OverlayConversation masterConversation={selectedConversation} isSelected={true} />
|
||||||
<Tooltip title="Show video!">
|
</>
|
||||||
<IconButton
|
|
||||||
sx={{
|
|
||||||
color: 'white',
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<FiVideoOff />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* todo: not speaking mode, speaking mode, locked in mode */}
|
|
||||||
<Tooltip title="Speak or toggle by clicking here!">
|
|
||||||
<IconButton
|
|
||||||
sx={{
|
|
||||||
color: 'white',
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<FiSun />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
sx={{
|
|
||||||
color: 'GrayText',
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<FiX />
|
|
||||||
</IconButton>
|
|
||||||
</Stack>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Stack sx={{ mt: 'auto' }} spacing={1} direction={'column'} alignItems={'center'}>
|
<Stack sx={{ mt: 'auto' }} spacing={1} direction={'column'} alignItems={'center'}>
|
||||||
@@ -222,6 +165,104 @@ export default function FooterControls() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function OverlayConversation({
|
||||||
|
masterConversation,
|
||||||
|
isSelected = false,
|
||||||
|
}: {
|
||||||
|
masterConversation: MasterConversation;
|
||||||
|
isSelected?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
direction={'column'}
|
||||||
|
spacing={1}
|
||||||
|
sx={{
|
||||||
|
p: 1,
|
||||||
|
borderRadius: 2,
|
||||||
|
bgcolor: isSelected ? blueGrey[300] : '',
|
||||||
|
}}
|
||||||
|
alignItems={'center'}
|
||||||
|
>
|
||||||
|
{/* status */}
|
||||||
|
<Box
|
||||||
|
component={'span'}
|
||||||
|
sx={{
|
||||||
|
bgcolor: (theme) => theme.palette.secondary.main,
|
||||||
|
height: 5,
|
||||||
|
width: 5,
|
||||||
|
borderRadius: 100,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AvatarGroup
|
||||||
|
variant={'rounded'}
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
'& >:first-child': {
|
||||||
|
marginTop: 0,
|
||||||
|
},
|
||||||
|
'& > *': {
|
||||||
|
marginLeft: '0 !important' as any,
|
||||||
|
marginTop: -2,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{masterConversation.members?.map((conversationUser, index) => (
|
||||||
|
<Avatar
|
||||||
|
key={`${masterConversation._id.toString()}-${conversationUser._id.toString()}-convoIcon`}
|
||||||
|
alt={conversationUser?.givenName}
|
||||||
|
src={conversationUser?.picture}
|
||||||
|
sx={{
|
||||||
|
opacity: masterConversation.tunedInUsers?.includes(conversationUser._id.toString())
|
||||||
|
? '100%'
|
||||||
|
: '20%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</AvatarGroup>
|
||||||
|
|
||||||
|
{isSelected && (
|
||||||
|
<>
|
||||||
|
<Divider orientation="horizontal" flexItem />
|
||||||
|
|
||||||
|
<Tooltip title="Show video!">
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<FiVideoOff />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* todo: not speaking mode, speaking mode, locked in mode */}
|
||||||
|
<Tooltip title="Speak or toggle by clicking here!">
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<FiSun />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'GrayText',
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<FiX />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const NO_DEVICE_SELECTION = 'NA';
|
const NO_DEVICE_SELECTION = 'NA';
|
||||||
|
|
||||||
function UserSettingsDialog({ open, handleClose }: { handleClose: () => void; open: boolean }) {
|
function UserSettingsDialog({ open, handleClose }: { handleClose: () => void; open: boolean }) {
|
||||||
|
|||||||
Reference in New Issue
Block a user