added chat drawer

This commit is contained in:
Carter 2024-12-04 10:41:46 -06:00
parent 7c3b0a3ab0
commit 1a9ee12533
7 changed files with 196 additions and 110 deletions

View file

@ -23,3 +23,16 @@ html, body, #root {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
/* styles.css or a CSS module */
.MuiDialog-root {
z-index: 1500 !important;
}
.MuiDrawer-root {
z-index: 1300 !important;
}
.MuiMenu-root {
z-index: 1400 !important; /* Ensure it's above the drawer */
}

View file

@ -2,6 +2,7 @@ import { Chat as ChatIcon, ChevronRight } from "@mui/icons-material";
import { Box, Drawer, IconButton, Theme, Tooltip, Typography } from "@mui/material"; import { Box, Drawer, IconButton, Theme, Tooltip, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import Chat from "chat/Chat"; import Chat from "chat/Chat";
import { ChatDrawer } from "chat/ChatDrawer";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
import { Team, User } from "models"; import { Team, User } from "models";
import moment from "moment"; import moment from "moment";
@ -36,7 +37,8 @@ const useStyles = makeStyles((theme: Theme) => ({
padding: theme.spacing(1), padding: theme.spacing(1),
height: "100%", height: "100%",
minWidth: theme.spacing(54), minWidth: theme.spacing(54),
maxWidth: theme.spacing(54) maxWidth: theme.spacing(54),
zIndex: 1000000000000
}, },
chatDrawerMobile: { chatDrawerMobile: {
padding: theme.spacing(1), padding: theme.spacing(1),
@ -119,22 +121,29 @@ export default function HeaderButtons(props: Props) {
<> <>
{chatButton()} {chatButton()}
{team && team.settings && team.settings.key && ( {team && team.settings && team.settings.key && (
<Drawer <ChatDrawer
anchor="right"
open={chatDrawerOpen} open={chatDrawerOpen}
onClose={() => setChatDrawerOpen(false)} onClose={() => setChatDrawerOpen(false)}
style={{ height: "100%", maxWidth: "100%" }} team={team}
title={team.settings.name ? team.settings.name + "Chat" : "Chat"}> />
<div className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}> // <Drawer
<div className={classes.drawerHeader}> // anchor="right"
<IconButton onClick={() => setChatDrawerOpen(false)}> // open={chatDrawerOpen}
<ChevronRight /> // sx={{ zIndex: 10000 }}
</IconButton> // onClose={() => setChatDrawerOpen(false)}
<Typography>{team.name()} Chat</Typography> // style={{ height: "100%", maxWidth: "100%" }}
</div> // title={team.settings.name ? team.settings.name + "Chat" : "Chat"}>
<Chat objectKey={team.settings.key} type={pond.NoteType.NOTE_TYPE_TEAM} />
</div> // <div className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}>
</Drawer> // <div className={classes.drawerHeader}>
// <IconButton onClick={() => setChatDrawerOpen(false)}>
// <ChevronRight />
// </IconButton>
// <Typography>{team.name()} Chat</Typography>
// </div>
// <Chat objectKey={team.settings.key} type={pond.NoteType.NOTE_TYPE_TEAM} />
// </div>
// </Drawer>
)} )}
</> </>
) )

View file

@ -1,4 +1,4 @@
import { Box, CircularProgress } from "@mui/material"; import { Box, CircularProgress, Theme } from "@mui/material";
import { useGlobalState, useNoteAPI, useTeamAPI } from "providers"; import { useGlobalState, useNoteAPI, useTeamAPI } from "providers";
import React from "react"; import React from "react";
import { useCallback } from "react"; import { useCallback } from "react";
@ -9,6 +9,7 @@ import ChatOutput from "./ChatOutput";
import { Note } from "models"; import { Note } from "models";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { makeStyles } from "@mui/styles";
interface Props { interface Props {
objectKey: string; objectKey: string;
@ -26,6 +27,7 @@ export default function Chat(props: Props) {
const [attachmentMap, setAttachmentMap] = useState<Map<string, pond.FileReference[]>>(new Map()); const [attachmentMap, setAttachmentMap] = useState<Map<string, pond.FileReference[]>>(new Map());
const teamAPI = useTeamAPI(); const teamAPI = useTeamAPI();
// const classes = useStyles();
const loadNotes = useCallback(() => { const loadNotes = useCallback(() => {
let currentNotes: Note[] = notes; let currentNotes: Note[] = notes;
@ -36,7 +38,6 @@ export default function Chat(props: Props) {
if (resp.data.notes.length < 1) { if (resp.data.notes.length < 1) {
return; return;
} }
setTotalMessages(resp.data.total); setTotalMessages(resp.data.total);
let tempNotes: Note[] = []; let tempNotes: Note[] = [];
let tempAttachmentMap: Map<string, pond.FileReference[]> = attachmentMap; let tempAttachmentMap: Map<string, pond.FileReference[]> = attachmentMap;

61
src/chat/ChatDrawer.tsx Normal file
View file

@ -0,0 +1,61 @@
import { ChevronRight } from "@mui/icons-material";
import { Box, Drawer, IconButton, Theme, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import { useMobile } from "hooks";
import { Team } from "models";
import { pond } from "protobuf-ts/pond";
import Chat from "./Chat";
const useStyles = makeStyles((theme: Theme) => {
return ({
chatDrawer: {
backgroundColor: theme.palette.background.default,
// padding: theme.spacing(1),
height: "100%",
minWidth: theme.spacing(54),
maxWidth: theme.spacing(54),
// zIndex: 1000000000000,
// zIndex: theme.zIndex.drawer,
},
chatDrawerMobile: {
backgroundColor: theme.palette.background.default,
// padding: theme.spacing(1),
height: "auto",
width: "100%"
},
drawerHeader: {
display: "flex",
alignItems: "center",
padding: theme.spacing(1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: "flex-start"
}
});
});
interface Props {
open: boolean;
onClose: () => void;
team: Team;
}
export function ChatDrawer(props: Props) {
const { open, onClose, team } = props;
const isMobile = useMobile();
const classes = useStyles()
return (
<Drawer open={open} onClose={onClose} anchor="right" sx={{ zIndex: 10000 }}>
<Box className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}>
<Box className={classes.drawerHeader}>
<IconButton onClick={onClose}>
<ChevronRight />
</IconButton>
<Typography>{team.name()} Chat</Typography>
</Box>
<Chat objectKey={team.settings.key} type={pond.NoteType.NOTE_TYPE_TEAM} />
</Box>
</Drawer>
)
}

View file

@ -1,4 +1,5 @@
import { import {
Box,
Button, Button,
//DialogActions, //DialogActions,
//DialogContent, //DialogContent,
@ -31,12 +32,22 @@ const useStyles = makeStyles((theme: Theme) => ({
margin: 8, margin: 8,
marginRight: 12 marginRight: 12
}, },
textAreaBox: {
width: "100%",
padding: theme.spacing(1),
paddingTop: theme.spacing(0),
},
textAreaClass: { textAreaClass: {
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)", padding: theme.spacing(1),
borderColor: getThemeType() === "light" ? "rgb(255, 255, 255)" : "rgb(60, 60, 60)", // backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)",
color: getThemeType() === "light" ? "rgb(10, 10, 10)" : "rgb(245, 245, 245)", // borderColor: getThemeType() === "light" ? "rgb(255, 255, 255)" : "rgb(60, 60, 60)",
// color: getThemeType() === "light" ? "rgb(10, 10, 10)" : "rgb(245, 245, 245)",
backgroundColor: theme.palette.background.paper,
borderColor: theme.palette.background.default,
color: theme.palette.text.primary,
borderRadius: "5px", borderRadius: "5px",
width: "100%" width: "100%",
} }
})) }))
@ -104,20 +115,19 @@ export default function ChatInput(props: Props) {
let newNote = Note.create(); let newNote = Note.create();
newNote.settings.objectKey = objectKey; newNote.settings.objectKey = objectKey;
newNote.settings.userId = user.id(); newNote.settings.userId = user.id();
//newNote.settings.objectType = pond.NoteType.NOTE_TYPE_TEAM not using type at the moment
if (type) { if (type) {
newNote.settings.objectType = type; newNote.settings.objectType = type;
} }
newNote.settings.timestamp = Date.now(); newNote.settings.timestamp = Date.now();
newNote.settings.content = message; newNote.settings.content = message;
noteAPI noteAPI
.addNote(newNote.settings, Array.from(uploadedFiles.keys())) .addNote(newNote.settings, Array.from(uploadedFiles.keys()))
.then(resp => { .then(resp => {
newNote.settings.key = resp.data.note
props.newNoteMethod(newNote); props.newNoteMethod(newNote);
openSnack("Message Sent"); openSnack("Message Sent");
}) })
.catch(err => { .catch(_err => {
openSnack("Message Failed to send"); openSnack("Message Failed to send");
}); });
} }
@ -175,19 +185,21 @@ export default function ChatInput(props: Props) {
justifyContent="space-between" justifyContent="space-between"
spacing={1} spacing={1}
style={{ marginTop: theme.spacing(1) }}> style={{ marginTop: theme.spacing(1) }}>
<Grid> {/* <Grid>
<Button onClick={validateMessage} variant="contained" color="primary"> <Button onClick={validateMessage} variant="contained" color="primary">
<ChatIcon /> <ChatIcon />
</Button> </Button>
</Grid> </Grid> */}
<Grid size={{ xs: 9 }} > <Grid size={{ xs: 12 }} >
<TextareaAutosize <Box className={classes.textAreaBox}>
value={message} <textarea
className={classes.textAreaClass} value={message}
onChange={e => setMessage(e.target.value)} className={classes.textAreaClass}
onKeyDown={onKeyDown} onChange={e => setMessage(e.target.value)}
onKeyUp={onKeyUp} onKeyDown={onKeyDown}
/> onKeyUp={onKeyUp}
/>
</Box>
</Grid> </Grid>
{/* <Grid item xs={2}> {/* <Grid item xs={2}>
<IconButton <IconButton

View file

@ -7,12 +7,11 @@ import {
DialogContent, DialogContent,
DialogTitle, DialogTitle,
Grid2 as Grid, Grid2 as Grid,
List, IconButton,
ListItem,
ListItemButton,
ListItemIcon, ListItemIcon,
ListItemText, ListItemText,
Menu, Menu,
MenuItem,
Theme, Theme,
Typography Typography
} from "@mui/material"; } from "@mui/material";
@ -39,21 +38,26 @@ interface Props {
attachments?: pond.FileReference[]; attachments?: pond.FileReference[];
removeNoteMethod: (index: number) => void; removeNoteMethod: (index: number) => void;
} }
const useStyles = makeStyles((_theme: Theme) => ({ const useStyles = makeStyles((theme: Theme) => ({
red: { red: {
color: red["500"] color: red["500"]
}, },
oddGrid: { oddGrid: {
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)", backgroundColor: theme.palette.background.paper,
padding: 4 padding: 4
}, },
evenGrid: { evenGrid: {
backgroundColor: getThemeType() === "light" ? "rgb(235, 235, 235)" : "rgb(60, 60, 60)", backgroundColor: theme.palette.background.default,
padding: 4 padding: 4
}, },
textContainer: {
padding: theme.spacing(1),
},
userAvatar: { userAvatar: {
width: 36, width: 36,
height: 36 height: 36,
marginLeft: theme.spacing(1),
marginRight: theme.spacing(2),
} }
})) }))
@ -83,14 +87,14 @@ export default function ChatMessage(props: Props) {
.then(resp => { .then(resp => {
setPermissions(resp.permissions); setPermissions(resp.permissions);
}) })
.catch(err => {}); .catch(_err => {});
userAPI userAPI
.getProfile(props.note.settings.userId) .getProfile(props.note.settings.userId)
.then(resp => { .then(resp => {
setName(resp.name); setName(resp.name);
setAvatar(resp.avatar); setAvatar(resp.avatar);
}) })
.catch(err => {}); .catch(_err => {});
}, [props.note, userAPI, user]); }, [props.note, userAPI, user]);
useEffect(() => { useEffect(() => {
@ -101,11 +105,11 @@ export default function ChatMessage(props: Props) {
setAnchorEl(null); setAnchorEl(null);
noteAPI noteAPI
.removeNote(props.note.key()) .removeNote(props.note.key())
.then(resp => { .then(_resp => {
openSnack("Message Deleted"); openSnack("Message Deleted");
props.removeNoteMethod(props.index); props.removeNoteMethod(props.index);
}) })
.catch(err => { .catch(_err => {
openSnack("Failed to delete Message"); openSnack("Failed to delete Message");
}); });
closeDialog(); closeDialog();
@ -174,27 +178,14 @@ export default function ChatMessage(props: Props) {
anchorEl={anchorEl} anchorEl={anchorEl}
open={Boolean(anchorEl)} open={Boolean(anchorEl)}
onClose={() => setAnchorEl(null)} onClose={() => setAnchorEl(null)}
keepMounted> keepMounted
<List> >
<ListItemButton onClick={openDialog}> <MenuItem dense onClick={openDialog}>
<ListItemIcon> <ListItemIcon>
<DeleteIcon className={classes.red} /> <DeleteIcon className={classes.red} />
</ListItemIcon> </ListItemIcon>
<ListItemText>Delete Message</ListItemText> <ListItemText>Delete Message</ListItemText>
</ListItemButton> </MenuItem>
{/* {props.attachments && props.attachments.length > 0 && (
<ListItem
button
onClick={() => {
setFileListOpen(true);
}}>
<ListItemIcon>
<Visibility />
</ListItemIcon>
<ListItemText>View Attachments</ListItemText>
</ListItem>
)} */}
</List>
</Menu> </Menu>
); );
}; };
@ -226,58 +217,47 @@ export default function ChatMessage(props: Props) {
alignItems="center" alignItems="center"
className={props.index % 2 === 0 ? classes.oddGrid : classes.evenGrid}> className={props.index % 2 === 0 ? classes.oddGrid : classes.evenGrid}>
<Grid> <Grid>
<Box marginLeft="5px"> {avatar ? (
{/* avatar */} <Avatar alt={name} src={avatar} className={classes.userAvatar}>
{avatar ? ( {!avatar && name}
<Avatar alt={name} src={avatar} className={classes.userAvatar}> </Avatar>
{!avatar && name} ) : (
</Avatar> <Avatar alt={name} className={classes.userAvatar}>
) : ( <Person />
<Avatar alt={name} className={classes.userAvatar}> </Avatar>
<Person /> )}
</Avatar>
)}
</Box>
</Grid> </Grid>
<Grid size={{ xs: 12 }}> <Grid container direction="column" justifyContent="space-between" size={{ xs:10 }} >
<Box marginLeft="5px"> <Grid container direction="row" alignContent={"center"} spacing={1} >
<Grid container direction="column" style={{ margin: "5px" }}> <Grid>
<Grid> <Typography color="textPrimary">
<Grid container direction="row"> {name}
<Grid> </Typography>
<Typography variant="subtitle2" color="textPrimary">
{name}
</Typography>
</Grid>
<Box marginRight={1} marginLeft={1}>
<Grid>
<Typography variant="caption" color="textSecondary">
{moment(props.note.date())
.tz(user.settings.timezone)
.format("MMMM Do YYYY, h:mm:ss a")}
</Typography>
</Grid>
</Box>
</Grid>
</Grid>
<Grid>
<Typography style={{ whiteSpace: "pre-wrap" }} variant="caption">
{props.note.content()}
</Typography>
{/*load files and display attached images below the message */}
</Grid>
</Grid> </Grid>
</Box> <Grid justifyContent={"center"}>
<Typography variant="caption" color="textSecondary">
{moment(props.note.date())
.tz(user.settings.timezone)
.format("MMMM Do YYYY, h:mm:ss a")}
</Typography>
</Grid>
</Grid>
<Grid>
<Typography variant="body2">
{props.note.content()}
</Typography>
{/*load files and display attached images below the message */}
</Grid>
</Grid> </Grid>
<Grid> <Grid padding={1}>
{/* delete button */} {/* delete button */}
{permissions.includes(pond.Permission.PERMISSION_WRITE) && ( {permissions.includes(pond.Permission.PERMISSION_WRITE) && (
<Button <IconButton
onClick={(event: React.MouseEvent<HTMLButtonElement>) => onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
setAnchorEl(event.currentTarget) setAnchorEl(event.currentTarget)
}> }>
<MoreVert /> <MoreVert />
</Button> </IconButton>
)} )}
</Grid> </Grid>
</Grid> </Grid>

View file

@ -120,6 +120,16 @@ function options(themeType: "light" | "dark"): ThemeOptions {
styleOverrides: { styleOverrides: {
paperFullScreen: { paperFullScreen: {
overflowX: "hidden" overflowX: "hidden"
},
root: {
zIndex: 1500
}
},
},
MuiDrawer: {
styleOverrides: {
root: {
zIndex: 1300
} }
}, },
}, },