diff --git a/packages/common/services/index.ts b/packages/common/services/index.ts index ebc8496..2903234 100644 --- a/packages/common/services/index.ts +++ b/packages/common/services/index.ts @@ -1,3 +1,6 @@ import ConversationService from "./conversationService"; +import UserService from "./userService"; export const conversationService = new ConversationService(); + +export const userService = new UserService(); diff --git a/packages/common/services/userService.tsx b/packages/common/services/userService.ts similarity index 97% rename from packages/common/services/userService.tsx rename to packages/common/services/userService.ts index dd69436..95cb710 100644 --- a/packages/common/services/userService.tsx +++ b/packages/common/services/userService.ts @@ -12,7 +12,7 @@ import { DocumentSnapshot, } from "firebase/firestore"; import { User, UserStatus } from "../models/user"; -import { Collections } from "./collections"; +import Collections from "./collections"; export default class UserService { private db: Firestore = getFirestore(); diff --git a/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx b/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx index c4376c8..22e4cd6 100644 --- a/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx +++ b/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx @@ -152,6 +152,7 @@ export default function CreateConversation() { }); // add myself to this collection of members + arrActiveMembers.push(currUser!.uid); members.push( new ConversationMember( currUser!.uid, diff --git a/packages/web/components/UserDetails/MasterAvatarGroup.tsx b/packages/web/components/UserDetails/MasterAvatarGroup.tsx new file mode 100644 index 0000000..e3810e4 --- /dev/null +++ b/packages/web/components/UserDetails/MasterAvatarGroup.tsx @@ -0,0 +1,60 @@ +import { User } from "@nirvana/common/models/user"; +import { useAuth } from "../../contexts/authContext"; +import { useRecoilValue, useRecoilState } from "recoil"; +import { allRelevantContactsAtom } from "../../recoil/main"; +import { userService } from "@nirvana/common/services"; + +export default function MasterAvatarGroup(props: { listOfUsers: User[] }) { + const contactsMap = useRecoilValue(allRelevantContactsAtom); + + /** + * Requirements: + * if one two users in the conversation: then just get the one that is not the currUser + * if three people => show icon of the other two people + * if 4 people => show 3 people in triangle fashion + * if 6 more 'other' people, then show 3 and then one circle saying how many more there are + */ + + const { currUser } = useAuth(); + + const listOfOtherUsers = props.listOfUsers.filter( + (lUser) => lUser.id != currUser!.uid + ); + + return ( + + {listOfOtherUsers.map((oUser) => ( + + ))} + + ); +} + +export function MasterAvatarGroupWithUserFetch(props: { + listOfUserIds: string[]; +}) { + const [contactsMap, setContactsMap] = useRecoilState(allRelevantContactsAtom); + + const { currUser } = useAuth(); + + const listOfOtherUsers = props.listOfUserIds.filter( + (lUser) => lUser != currUser!.uid + ); + + const resultUsers: User[] = [] as User[]; + + listOfOtherUsers.forEach(async (oUser) => { + if (contactsMap.has(oUser) && contactsMap.get(oUser) instanceof User) { + resultUsers.push(contactsMap.get(oUser)!); + } else { + // otherwise, fetch document from firestore and then set the cache + const retrievedUser = await userService.getUser(oUser); + + if (retrievedUser) { + setContactsMap(new Map(contactsMap.set(oUser, retrievedUser))); + } + } + }); + + return ; +} diff --git a/packages/web/recoil/MainRecoilDataHandler.tsx b/packages/web/recoil/MainRecoilDataHandler.tsx index c452971..3d4ffc5 100644 --- a/packages/web/recoil/MainRecoilDataHandler.tsx +++ b/packages/web/recoil/MainRecoilDataHandler.tsx @@ -15,23 +15,23 @@ import { } from "firebase/firestore"; import { useEffect } from "react"; import toast from "react-hot-toast"; -import { useRecoilState, useRecoilValue } from "recoil"; +import { useRecoilState } from "recoil"; import { useAuth } from "../contexts/authContext"; import { - allRelevantConversations, - allUsersConversations, + allRelevantConversationsAtom, + allUsersConversationsAtom, nirvanaUserDataAtom, } from "./main"; -const db = getFirestore(); +import { firestoreDb as db } from "../services/firebaseService"; export default function MainRecoilDataHandler() { const { currUser } = useAuth(); const [nirvanaUser, setNirvanaUser] = useRecoilState(nirvanaUserDataAtom); - const [userConvos, setUserConvos] = useRecoilState(allUsersConversations); + const [userConvos, setUserConvos] = useRecoilState(allUsersConversationsAtom); const [relevantConvos, setRelevantConvos] = useRecoilState( - allRelevantConversations + allRelevantConversationsAtom ); useEffect(() => { @@ -51,16 +51,17 @@ export default function MainRecoilDataHandler() { newConvoMember.id = change.doc.id; if (change.type === "added" || change.type === "modified") { + // if we are adding a new convo relevant to me, then start a convo listener for this convo + const convoId = change.doc.ref.parent.parent!.id; + // update all user convo associations setUserConvos((prevMap) => { - return new Map(prevMap.set(newConvoMember.id, newConvoMember)); + return new Map(prevMap.set(convoId, newConvoMember)); }); // if we are adding a new convo relevant to me, then start a convo listener for this convo - const convoId = change.doc.ref.parent.parent?.id; // find if this is a done, later, or inbox, and only add listeners accordingly - console.log(convoId); // if (change.type === "added" && convoId) { // const unsubConvo = onSnapshot( // doc(db, Collections.conversations, convoId), diff --git a/packages/web/recoil/main.tsx b/packages/web/recoil/main.tsx index 17a2abc..5a72a30 100644 --- a/packages/web/recoil/main.tsx +++ b/packages/web/recoil/main.tsx @@ -6,7 +6,7 @@ import Conversation, { } from "@nirvana/common/models/conversation"; import { User as NirvanaUser } from "@nirvana/common/models/user"; -import { atom, selector } from "recoil"; +import { atom, selector, selectorFamily } from "recoil"; export enum RecoilActions { TEST = "TEST", @@ -20,6 +20,9 @@ export enum RecoilActions { SORTED_CONVERSATIONS = "SORTED_CONVERSATIONS", LIVE_ROOMS = "LIVE_ROOMS", + ALL_RELEVANT_CONTACTS = "ALL_RELEVANT_CONTACTS", + RELEVANT_CONTACTS_SELECTOR_CACHE = "RELEVANT_CONTACTS_SELECTOR_CACHE", + USER_DATA = "USER_DATA", } @@ -38,60 +41,30 @@ export const nirvanaUserDataAtom = atom({ // default: getTest, // default value (aka initial value) // }); -export class CompleteConversation { - id: string; // conversation id - - get isLive(): boolean { - return this.members?.length > 0; - } - - // method/property for knowing if this there is a new activity for me in this conversation - - // method/property for getting the latest link if valid - - // method/property to get the latest convo chunk/last person talking - - conversation: Conversation; - - constructor(convo: Conversation) { - this.conversation = convo; - this.id = this.conversation.id; - } - - userMember?: ConversationMember; - - members: ConversationMember[] = [] as ConversationMember[]; - audioClips: AudioClip[] = [] as AudioClip[]; - links: Link[] = [] as Link[]; -} - -// map cache of all complete conversation objects -export const allCompleteConversations = atom({ - key: RecoilActions.ALL_COMPLETE_CONVERSATIONS, - default: new Map(), -}); - -// map cache of all relevant users - -// selector for the convos that have members in the live room - -// selectors for convos: inbox/default, live, later, done, priority - // approach: fill in all "bottom" level atoms, and then build the tree later with selectors -export const allUsersConversations = atom({ +// convo id -> userMember object +export const allUsersConversationsAtom = atom({ key: RecoilActions.ALL_USERS_CONVERSATION_RELATIONSHIPS, default: new Map(), }); -export const allRelevantConversations = atom({ +export const allRelevantConversationsAtom = atom({ key: RecoilActions.ALL_RELEVANT_CONVERSATIONS, default: new Map(), + effects: [ + ({ onSet }) => { + onSet((newMap) => { + //go through and make sure that our relevant user's cache is up to date + // const allUsersInConvos = newMap.values() + }); + }, + ], }); export const sortedRoomSelector = selector({ key: RecoilActions.SORTED_CONVERSATIONS, get: ({ get }) => { - const relConvos = get(allRelevantConversations); + const relConvos = get(allRelevantConversationsAtom); const convosArr: Conversation[] = Array.from(relConvos.values()); convosArr.sort((a, b) => { @@ -113,7 +86,9 @@ export const sortedRoomSelector = selector({ }, }); -// get all rooms with active members in the room, and +// selectors for convos: inbox/default, live, later, done, priority + +// get all rooms with active members in the room export const liveRoomsSelector = selector({ key: RecoilActions.LIVE_ROOMS, get: ({ get }) => { @@ -125,3 +100,21 @@ export const liveRoomsSelector = selector({ return liveRooms; }, }); + +// map cache of all relevant users +// get all of the users in all of the conversations, once through a simple service call +export const allRelevantContactsAtom = atom>({ + key: RecoilActions.ALL_RELEVANT_CONTACTS, + default: new Map(), +}); + +// todo: useful selector where a component can pass in a list of users +// and return their full information +export const cachedRelevantContactsSelector = selector({ + key: RecoilActions.RELEVANT_CONTACTS_SELECTOR_CACHE, + get: async ({ get }) => { + const currContacts: Map = get(allRelevantContactsAtom); + return []; + }, + // selector set (pass in a user and build cache if somethings not in the cache already) +}); diff --git a/packages/web/services/firebaseService.js b/packages/web/services/firebaseService.js index ef6bedf..1f12522 100644 --- a/packages/web/services/firebaseService.js +++ b/packages/web/services/firebaseService.js @@ -40,8 +40,9 @@ console.log("initialized firebase"); // const analytics = getAnalytics(app); // Initialize firestore persistence for data caching -const db = getFirestore(app); -enableIndexedDbPersistence(db).catch((err) => { +export const firestoreDb = getFirestore(app); + +enableIndexedDbPersistence(firestoreDb).catch((err) => { if (err.code == "failed-precondition") { // Multiple tabs open, persistence can only be enabled // in one tab at a a time.