using chats instead of notes for the chat

This commit is contained in:
Carter 2024-12-05 20:53:57 -06:00
parent 9ec1956687
commit cf45fcb98a
9 changed files with 201 additions and 284 deletions

View file

@ -1,4 +1,4 @@
import { Box, CircularProgress, Theme } from "@mui/material";
import { Box, CircularProgress } from "@mui/material";
import { useGlobalState, useNoteAPI, useTeamAPI } from "providers";
import React from "react";
import { useCallback } from "react";
@ -9,7 +9,6 @@ import ChatOutput from "./ChatOutput";
import { Note } from "models";
import { pond } from "protobuf-ts/pond";
import moment from "moment-timezone";
import { makeStyles } from "@mui/styles";
interface Props {
objectKey: string;
@ -17,95 +16,73 @@ interface Props {
}
export default function Chat(props: Props) {
const [notes, setNotes] = useState<Note[]>([]);
const [chats, setChats] = useState<pond.Chat[]>([]);
const { objectKey, type } = props;
const noteAPI = useNoteAPI();
const [notesLoaded, setNotesLoaded] = useState<boolean>(false);
const [loaded, setLoaded] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const [totalMessages, setTotalMessages] = useState(0);
const [scrollPos, setScrollPos] = useState(0);
const [{ team }] = useGlobalState();
const [{ user }] = useGlobalState();
const [attachmentMap, setAttachmentMap] = useState<Map<string, pond.FileReference[]>>(new Map());
const teamAPI = useTeamAPI();
// const classes = useStyles();
const loadNotes = useCallback(() => {
let currentNotes: Note[] = notes;
noteAPI
.listNotes(15, currentNotes.length, "desc", "timestamp", objectKey)
.then(resp => {
setNotesLoaded(true);
if (resp.data.notes.length < 1) {
return;
}
setTotalMessages(resp.data.total);
let tempNotes: Note[] = [];
let tempAttachmentMap: Map<string, pond.FileReference[]> = attachmentMap;
resp.data.notes.forEach(note => {
let n = Note.any(note);
tempNotes.push(n);
let refList = resp.data.attachments[n.key()];
if (refList) {
tempAttachmentMap.set(n.key(), refList.refs);
}
});
setScrollPos(tempNotes.length);
currentNotes = currentNotes.concat(tempNotes);
currentNotes.reverse();
setNotes(currentNotes);
setAttachmentMap(tempAttachmentMap);
useEffect(() => {
console.log(totalMessages)
}, [totalMessages])
if (type) {
// Setting the note type for future use
currentNotes.forEach(note => {
if (note.settings.objectType === pond.NoteType.NOTE_TYPE_UNKNOWN && type) {
note.settings.objectType = type;
noteAPI.updateNote(note.settings);
}
});
// Update new chat viewed timestamp
if (type === pond.NoteType.NOTE_TYPE_TEAM) {
team.preferences.chatViewedTimestamp = moment().toISOString();
teamAPI.updatePreferences(team.key(), team.preferences);
}
}
})
.catch(_err => {});
}, [noteAPI, objectKey, notes, type, team, teamAPI, attachmentMap]);
const loadChats = () => {
setLoading(true)
noteAPI.listChats(10, chats.length, "desc", "timestamp", objectKey).then(resp => {
setChats(resp.data.chats ? resp.data.chats.reverse().concat(chats) : [])
setTotalMessages(resp.data.total)
}).finally(() => {
setLoading(false)
setLoaded(true)
})
}
const removeNote = (index: number) => {
let n = notes;
let c = chats
setTotalMessages(totalMessages - 1);
n.splice(index, 1);
setNotes([...n]);
c.splice(index, 1);
setChats([...c])
};
const showNewNotes = (newNote: Note) => {
let n = notes;
n.push(newNote);
setScrollPos(n.length);
setNotes([...n]);
let note = pond.Note.create({
settings: newNote.settings,
status: newNote.status,
})
let profile = pond.UserProfile.create({
name: user.name(),
avatar: user.settings.avatar,
})
let permissions = [
pond.Permission.PERMISSION_READ,
pond.Permission.PERMISSION_WRITE,
]
let c = chats;
c.push({
note: note,
profile: profile,
permissions: permissions
} as any)
setChats([...c])
};
useEffect(() => {
if (!notesLoaded) {
loadNotes();
}
// setNotesLoaded(true);
}, [notesLoaded, loadNotes]);
loadChats()
}, []);
const loadMore = () => {
let n = notes;
n.reverse();
setNotes(n);
loadNotes();
loadChats();
};
return (
<React.Fragment>
{!notesLoaded ? (
<Box marginTop={"51%"} marginLeft={"44%"}>
{ loading && !loaded ? (
<Box width="100%" height="100%" alignContent={"center"} textAlign={"center"} >
<CircularProgress />
</Box>
) : (
@ -114,7 +91,7 @@ export default function Chat(props: Props) {
<ChatOutput
totalMessages={totalMessages}
attachmentMap={attachmentMap}
messages={notes}
messages={chats}
scrollPos={scrollPos}
removeNoteMethod={removeNote}
loadMore={loadMore}

View file

@ -1,12 +1,6 @@
import {
Box,
Button,
//DialogActions,
//DialogContent,
//DialogTitle,
Grid2 as Grid,
//IconButton,
TextareaAutosize,
Theme,
Typography,
useTheme
@ -15,10 +9,8 @@ import {
import { useGlobalState, useNoteAPI, useSnackbar } from "providers";
import React, { useState } from "react";
// import { getThemeType } from "theme";
import ChatIcon from "@mui/icons-material/Chat";
import { Note } from "models";
import { pond } from "protobuf-ts/pond";
import { getThemeType } from "theme/themeType";
import { makeStyles } from "@mui/styles";
//import { AttachFile } from "@material-ui/icons";
//import ResponsiveDialog from "common/ResponsiveDialog";
@ -134,47 +126,6 @@ export default function ChatInput(props: Props) {
clearEntry();
};
/**
* Dialog to select attachments
* postponing note attachments until notes can be overhauled to be more in line with the rest of the objects
* @returns
*/
// const attachmentDialog = () => {
// return (
// <ResponsiveDialog
// open={attachmentDialogOpen}
// onClose={() => {
// setAttachmentDialogOpen(false);
// }}>
// <DialogTitle>Add Attachments</DialogTitle>
// <DialogContent>
// <FileUploader
// uploadStart={() => {
// setUploadingFile(true);
// }}
// uploadEnd={(fileID, fileName) => {
// setUploadingFile(false);
// if (fileID && fileName) {
// let f = cloneDeep(uploadedFiles);
// f.set(fileID, fileName);
// setUploadedFiles(f);
// }
// }}
// />
// </DialogContent>
// <DialogActions>
// <Button
// disabled={uploadingFile}
// onClick={() => {
// setAttachmentDialogOpen(false);
// }}>
// Close
// </Button>
// </DialogActions>
// </ResponsiveDialog>
// );
// };
return (
<React.Fragment>
<Grid
@ -185,11 +136,6 @@ export default function ChatInput(props: Props) {
justifyContent="space-between"
spacing={1}
style={{ marginTop: theme.spacing(1) }}>
{/* <Grid>
<Button onClick={validateMessage} variant="contained" color="primary">
<ChatIcon />
</Button>
</Grid> */}
<Grid size={{ xs: 12 }} >
<Box className={classes.textAreaBox}>
<textarea
@ -201,15 +147,6 @@ export default function ChatInput(props: Props) {
/>
</Box>
</Grid>
{/* <Grid item xs={2}>
<IconButton
onClick={() => {
setAttachmentDialogOpen(true);
}}
color="primary">
<AttachFile />
</IconButton>
</Grid> */}
</Grid>
<Typography>{Array.from(uploadedFiles.values()).toString()}</Typography>
</React.Fragment>

View file

@ -17,24 +17,15 @@ import {
} from "@mui/material";
import { MoreVert, Person, Delete as DeleteIcon, } from "@mui/icons-material";
import moment from "moment-timezone";
import { useGlobalState, useNoteAPI, useSnackbar, useUserAPI } from "providers";
import { useGlobalState, useNoteAPI, useSnackbar } from "providers";
import React, { useState } from "react";
// import { getThemeType } from "theme";
// import { red } from "@material-ui/core/colors";
import { useEffect } from "react";
import { noteScope } from "models/Scope";
import { pond } from "protobuf-ts/pond";
import { useCallback } from "react";
import { Note } from "models";
import { red } from "@mui/material/colors";
import { getThemeType } from "theme/themeType";
import { makeStyles } from "@mui/styles";
//import ImageViewer from "common/ImageViewer";
//import ResponsiveDialog from "common/ResponsiveDialog";
interface Props {
index: number;
note: Note;
chat: pond.Chat;
attachments?: pond.FileReference[];
removeNoteMethod: (index: number) => void;
}
@ -44,11 +35,13 @@ const useStyles = makeStyles((theme: Theme) => ({
},
oddGrid: {
backgroundColor: theme.palette.background.paper,
padding: 4
// padding: 4,
padding: theme.spacing(1)
},
evenGrid: {
backgroundColor: theme.palette.background.default,
padding: 4
// backgroundColor: theme.palette.background.default,
// padding: 4
padding: theme.spacing(1)
},
textContainer: {
padding: theme.spacing(1),
@ -61,6 +54,8 @@ const useStyles = makeStyles((theme: Theme) => ({
}
}))
/**
* Takes in a note and renders it
* handles deleting of the note
@ -71,40 +66,33 @@ export default function ChatMessage(props: Props) {
const [{ user }] = useGlobalState();
const noteAPI = useNoteAPI();
const { openSnack } = useSnackbar();
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
const userAPI = useUserAPI();
const [dialog, setDialog] = useState(false);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
//const [fileListOpen, setFileListOpen] = useState(false);
const [name, setName] = useState("");
const [avatar, setAvatar] = useState("");
const avatar = props.chat.profile?.avatar
const name = props.chat.profile?.name
const timestamp = props.chat.note?.settings?.timestamp ? props.chat.note?.settings?.timestamp : undefined
const loadPermissions = useCallback(() => {
let scope = noteScope(props.note.key());
userAPI
.getUser(user.id(), scope)
.then(resp => {
setPermissions(resp.permissions);
})
.catch(_err => {});
userAPI
.getProfile(props.note.settings.userId)
.then(resp => {
setName(resp.name);
setAvatar(resp.avatar);
})
.catch(_err => {});
}, [props.note, userAPI, user]);
useEffect(() => {
loadPermissions();
}, [loadPermissions]);
const generatePermissions = () => {
let perms: pond.Permission[] = []
if (Array.isArray(props.chat.permissions)) {
perms = props.chat.permissions.map((perm: unknown) => {
const mappedPerm = pond.Permission[perm as keyof typeof pond.Permission];
return mappedPerm; // Return null for invalid keys
}).filter(Boolean); // Optionally filter out any null values
} else {
console.error("data.permissions is not an array or does not exist");
perms = [];
}
return perms
}
const permissions = generatePermissions()
const deleteMessage = () => {
if (!props.chat?.note?.settings) return
setAnchorEl(null);
noteAPI
.removeNote(props.note.key())
.removeNote(props.chat.note.settings.key)
.then(_resp => {
openSnack("Message Deleted");
props.removeNoteMethod(props.index);
@ -123,54 +111,6 @@ export default function ChatMessage(props: Props) {
setDialog(false);
};
// const uploadedImages = () => {
// return (
// <Grid container direction="row">
// {props.attachments?.map(attachment => {
// if (attachment.metadata && attachment.metadata.contentType.split("/")[0] === "image") {
// return (
// <Grid item xs={4} key={attachment.uuid}>
// <ImageViewer image={attachment} width={"100%"} />
// </Grid>
// );
// } else {
// return undefined;
// }
// })}
// </Grid>
// );
// };
// const fileListDialog = () => {
// return (
// <ResponsiveDialog
// open={fileListOpen}
// onClose={() => {
// setFileListOpen(false);
// }}>
// <DialogTitle>Message Attachments</DialogTitle>
// <DialogContent>
// {props.attachments &&
// props.attachments.map(file => {
// if (file.metadata && file.metadata.contentType.includes("image/")) {
// //if the file is an image
// return (
// <Box margin={2} key={file.uuid}>
// <ImageViewer image={file} width={"100%"} />
// </Box>
// );
// } else {
// return undefined;
// }
// })}
// </DialogContent>
// <DialogActions>
// <Button onClick={() => setFileListOpen(false)}>Close</Button>
// </DialogActions>
// </ResponsiveDialog>
// );
// };
const messageMenu = () => {
return (
<Menu
@ -236,28 +176,36 @@ export default function ChatMessage(props: Props) {
</Grid>
<Grid justifyContent={"center"}>
<Typography variant="caption" color="textSecondary">
{moment(props.note.date())
.tz(user.settings.timezone)
{moment.unix(timestamp ? timestamp/1000: 0)
.tz(user.settings.timezone ? user.settings.timezone : "Canada/Central")
.format("MMMM Do YYYY, h:mm:ss a")}
</Typography>
</Grid>
</Grid>
<Grid size={{ xs: 12 }}>
<Typography variant="body2" whiteSpace={"pre-line"}>
{props.note.content()}
{props.chat.note?.settings?.content}
</Typography>
{/*load files and display attached images below the message */}
</Grid>
</Grid>
<Grid padding={1}>
{/* delete button */}
{permissions.includes(pond.Permission.PERMISSION_WRITE) && (
{permissions.includes(pond.Permission.PERMISSION_WRITE) ? (
<IconButton
onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
setAnchorEl(event.currentTarget)
}>
<MoreVert />
</IconButton>
) : (
// <span>
// <Tooltip title="No permissions to edit or delete" disableInteractive>
<IconButton disabled>
<MoreVert />
</IconButton>
// </Tooltip>
// </span>
)}
</Grid>
</Grid>

View file

@ -1,12 +1,12 @@
import { useEffect, useRef } from "react";
import ChatMessage from "./ChatMessage";
import { Box, Button } from "@mui/material";
import { Box, Button, ListItemButton } from "@mui/material";
import { Note } from "models";
// import { Note } from "models";
import { pond } from "protobuf-ts/pond";
interface Props {
messages: Note[];
messages: pond.Chat[];
attachmentMap?: Map<string, pond.FileReference[]>;
totalMessages: number;
scrollPos: number;
@ -54,33 +54,31 @@ export default function ChatOutput(props: Props) {
let display = props.messages.map((curMessage, index) => (
<div key={index}>
<ChatMessage
note={curMessage}
chat={curMessage}
index={index}
attachments={props.attachmentMap?.get(curMessage.key())}
// attachments={props.attachmentMap?.get(curMessage.note?.settings?.key)}
removeNoteMethod={props.removeNoteMethod}
/>
{props.scrollPos !== 0 && index === props.scrollPos && <ScrollTo />}
{/* {props.scrollPos !== 0 && index === props.scrollPos && <ScrollTo />} */}
<ScrollTo />
</div>
));
return display;
};
return (
<Box
//height="85%"
// height="auto"
style={{
overflowY: "scroll",
overflowX: "hidden",
// borderRadius: "4px"
}}>
<Box style={{
overflowY: "scroll",
overflowX: "hidden",
}}>
{props.messages.length < props.totalMessages && (
<div style={{ textAlign: "center" }}>
<Button onClick={seeMore}>See More</Button>
</div>
<ListItemButton onClick={seeMore} sx={{ justifyContent: "center"}}>
<Button >See More</Button>
</ListItemButton>
)}
{displayMessages()}
{props.scrollPos === 0 && <ScrollTo />}
{/* <ScrollTo/> */}
</Box>
);
}