diff --git a/client/src/components/lib/BoardScroll.tsx b/client/src/components/lib/BoardScroll.tsx index afa4730..cc94bca 100644 --- a/client/src/components/lib/BoardScroll.tsx +++ b/client/src/components/lib/BoardScroll.tsx @@ -106,10 +106,16 @@ export const BoardScroll = () => { useEffect(() => { setBoardMeta({ title: '', id: '' }); }, [folder]); + const dup = new Set(); const sortedCavases = ( searchCanvases.length === 0 ? canvases : searchCanvases ) - .filter((board) => (folder === 'Recent' ? true : folder === board.folder)) + .filter( + (board) => + (folder === 'Recent' ? true : folder === board.folder) && + !dup.has(board.id) && + dup.add(board.id), + ) .sort((a, b) => { const dateA = new Date(a.updatedAt); const dateB = new Date(b.updatedAt); @@ -144,9 +150,10 @@ export const BoardScroll = () => { // Rerender on isImagePlaceds change to update the thumbnail }, [state, fileIds]); - const isOwner = - boardMeta.users.find((user) => user.collabID === boardMeta.collabID) - ?.permission === 'owner'; + const isOwner = boardMeta.users[0] + ? boardMeta.users.find((user) => user.collabID === boardMeta.collabID) + ?.permission === 'owner' + : true; return (
diff --git a/client/src/components/lib/IconDropDown.tsx b/client/src/components/lib/IconDropDown.tsx index eed8f96..1d8dca6 100644 --- a/client/src/components/lib/IconDropDown.tsx +++ b/client/src/components/lib/IconDropDown.tsx @@ -51,7 +51,7 @@ export const IconDropDown = () => { className="text-violet11 leading-1 flex h-full w-full items-center justify-center bg-white text-[15px] font-medium" delayMs={600} > - JD + {userFirstName[0] + userLastName[0]}
diff --git a/client/src/components/lib/UserList/UserList.tsx b/client/src/components/lib/UserList/UserList.tsx index 8642394..731bcd6 100644 --- a/client/src/components/lib/UserList/UserList.tsx +++ b/client/src/components/lib/UserList/UserList.tsx @@ -30,58 +30,63 @@ const UserList = () => { return (
- {Object.values(activeTenants).map((user, index) => ( -
focusedIndex - ? 'translate-x-6 -ml-3' - : '-translate-x-6 -ml-3' - : '-ml-3' - } `} - > - { + return ( +
focusedIndex + ? 'translate-x-6 -ml-3' + : '-translate-x-6 -ml-3' + : '-ml-3' + } `} > - {/* Add a blinking animation to the streamer, if any. */} - handleAvatarHover(index)} - onMouseLeave={handleAvatarLeave} - style={{ - border: - user.email === activeProducerID - ? '' - : `0.2rem solid ${user.outlineColor ?? 'white'}`, - }} + side="bottom" + sideOffset={5} > - - {user.initials} - - -
- ))} + {/* Add a blinking animation to the streamer, if any. */} + handleAvatarHover(index)} + onMouseLeave={handleAvatarLeave} + style={{ + border: + user.email === activeProducerID + ? '' + : `0.2rem solid ${user.outlineColor ?? 'white'}`, + }} + > + + {user.initials} + +
+
+ ); + })}
); }; diff --git a/client/src/hooks/useSocket.tsx b/client/src/hooks/useSocket.tsx index 32e78b4..f471c17 100644 --- a/client/src/hooks/useSocket.tsx +++ b/client/src/hooks/useSocket.tsx @@ -20,6 +20,7 @@ import axios from 'axios'; import { REST } from '@/constants'; import { createStateWithRoughElement } from '@/components/lib/BoardScroll'; import { fetchImageFromFirebaseStorage } from '@/views/SignInPage'; +import { useAppStore } from '@/stores/AppStore'; /** * Defines a hook that controls all socket related activities @@ -112,6 +113,7 @@ export const useSocket = () => { 'addUser', 'updateAvatars', ]); + const { setIsViewingComments } = useAppStore(['setIsViewingComments']); const socket = useRef(); @@ -298,6 +300,7 @@ export const useSocket = () => { if (socket.current?.room !== null) { socket.current?.leaveRoom(); } + setIsViewingComments(false); } else { socket.current?.joinRoom(roomID, boardMeta.collabID); } diff --git a/client/src/views/SignInPage.tsx b/client/src/views/SignInPage.tsx index bf6bc78..3174545 100644 --- a/client/src/views/SignInPage.tsx +++ b/client/src/views/SignInPage.tsx @@ -31,9 +31,12 @@ import { import { getStorage, ref, getDownloadURL } from 'firebase/storage'; import { createStateWithRoughElement } from '@/components/lib/BoardScroll'; import { + CanvasElement, CanvasElementState, useCanvasElementStore, } from '@/stores/CanvasElementsStore'; +import { commitImageToCache, getImageDataUrl } from '@/lib/image'; +import { BinaryFileData } from '@/types'; /** * It is the sign in page where user either inputs email and password or @@ -94,6 +97,11 @@ export async function getUserDetails( }>, ) => void, setCanvasElementState: (element: CanvasElementState) => void, + editCanvasElement: ( + id: string, + partialElement: Partial, + isLive?: boolean | undefined, + ) => void, ) { try { const user = await axios.get(REST.user.get, { @@ -120,6 +128,7 @@ export async function getUserDetails( setCanvases, setBoardMeta, setCanvasElementState, + editCanvasElement, ); } catch (error) { console.error('Error:', error); @@ -141,9 +150,15 @@ export const checkURL = async ( collabID: string; users: SharedUser[]; permission: string; + collaboratorAvatarUrls: Record; }>, ) => void, setCanvasElementState: (element: CanvasElementState) => void, + editCanvasElement: ( + id: string, + partialElement: Partial, + isLive?: boolean | undefined, + ) => void, signUp = false, ) => { const queryParams = new URLSearchParams(window.location.search); @@ -160,6 +175,35 @@ export const checkURL = async ( console.log('users ', board.data.users); console.log('permissions', board.data.permission); console.log(board); + + const collaboratorAvatarMeta = ( + await axios.put(REST.collaborators.getAvatar, { + collaboratorIds: board.data.collaborators, + }) + ).data.collaborators; + const collaboratorAvatarUrls = await Promise.all( + Object.entries( + collaboratorAvatarMeta as { + id: string; + avatar: string; + }, + ).map(async ([id, avatar]) => ({ + id, + avatar: (avatar ?? '').includes('https') + ? avatar + : await fetchImageFromFirebaseStorage( + `profilePictures/${avatar}.jpg`, + ), + })), + ); + const collaboratorAvatarUrlsMap = collaboratorAvatarUrls.reduce( + (acc, { id, avatar }) => { + id && avatar && (acc[id] = avatar); + return acc; + }, + {} as Record, + ) as Record; + setBoardMeta({ roomID: board.data.roomID, title: board.data.title, @@ -171,12 +215,43 @@ export const checkURL = async ( collabID: board.data.collabID, users: board.data.users, permission: board.data.permission, + collaboratorAvatarUrls: collaboratorAvatarUrlsMap, }); + // Fetch images from firebase storage + Object.entries(board.data.serialized.fileIds).forEach( + async ([elemId, fileId]) => { + const imageUrl = await fetchImageFromFirebaseStorage( + `boardImages/${fileId}.jpg`, + ); + const dataUrl = imageUrl && (await getImageDataUrl(imageUrl)); + if (!dataUrl) + throw new Error('Failed to resolve saved image dataurls'); + + const binary = { + dataURL: dataUrl, + id: fileId, + mimeType: 'image/jpeg', + } as BinaryFileData; + + const imageElement = { id: elemId }; + commitImageToCache( + { + ...binary, + lastRetrieved: Date.now(), + }, + imageElement, + // Will set fileIds, triggering a rerender. A placeholder + // will be shown in the mean time. + editCanvasElement, + ); + }, + ); + setCanvasElementState(createStateWithRoughElement(board.data.serialized)); isSharedCanvas = true; - } catch { - console.log('error'); + } catch (e: unknown) { + console.log(e); } //remove variable from url @@ -211,8 +286,9 @@ export default function SignInPage() { 'setCanvases', 'setBoardMeta', ]); - const { setCanvasElementState } = useCanvasElementStore([ + const { setCanvasElementState, editCanvasElement } = useCanvasElementStore([ 'setCanvasElementState', + 'editCanvasElement', ]); const emailRef = useRef(null); const passwordRef = useRef(null); @@ -236,6 +312,7 @@ export default function SignInPage() { setCanvases, setBoardMeta, setCanvasElementState, + editCanvasElement, ) )?.valueOf(); //get name, email, avatar of user @@ -284,6 +361,7 @@ export default function SignInPage() { setCanvases, setBoardMeta, setCanvasElementState, + editCanvasElement, ) ).valueOf(); diff --git a/client/src/views/Viewport.tsx b/client/src/views/Viewport.tsx index 274bfc7..52baeb2 100644 --- a/client/src/views/Viewport.tsx +++ b/client/src/views/Viewport.tsx @@ -110,7 +110,7 @@ const Viewport = () => {