From 20d5be0b1199ae8a8fdc09be1a68eb8cfdec0787 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Mon, 30 Dec 2024 13:02:58 +0000 Subject: [PATCH 01/22] Enable opening and closing sessions from the frontend Co-authored-by: Rodrigo Coelho --- src/api/socket.ts | 14 ++++++ .../sidebar/sessionController/CollabModal.tsx | 43 +++++++++++-------- .../CollaborativeSession.tsx | 4 +- src/contexts/CollabSessionContext.tsx | 4 +- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/api/socket.ts b/src/api/socket.ts index d516de0d..e27324ae 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -29,19 +29,29 @@ class OptionalSocket { class SessionsSocket { private url: string; private socket: OptionalSocket; + private _roomId: number | null; constructor(url: string) { this.url = url; this.socket = new OptionalSocket(); } + get roomId() { + return this._roomId; + } + connect() { const newSocket = io(this.url, { auth: { token: 'dummy', // TODO: Replace with actual federated authentication token } }); + this.socket.set(newSocket); + newSocket.on('connected', data => { + console.log('Connected to sessions socket'); + this._roomId = data['room_id']; + }); } disconnect() { @@ -53,6 +63,10 @@ class SessionsSocket { this.socket.use(socket => socket.on(event, callback)); } + onAny(callback: (event: string, ...args: any[]) => void) { + this.socket.use(socket => socket.onAny(callback)); + } + off(event: string, callback?: (...args: any[]) => void) { this.socket.use(socket => socket.off(event, callback)); } diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 0d93e8a3..6eb46218 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -4,6 +4,7 @@ import { XMarkIcon } from '@heroicons/react/24/solid'; import CollabPickSession from './CollabPickSession'; import CollabSessionModal from './CollabSessionModal'; import CollabSessionContext from '../../../../contexts/CollabSessionContext'; +import { sessionsSocket } from '../../../../api/socket'; const PICK_SESSION = 'PICK_SESSION'; const SESSION = 'SESSION'; @@ -15,43 +16,46 @@ type Props = { } const CollabModal = ({ isOpen, closeModal }: Props) => { - const { sessions, setSessions, currentSessionId, setcurrentSessionId } = useContext(CollabSessionContext); + const { sessions, setSessions, currentSessionId, setCurrentSessionId } = useContext(CollabSessionContext); const [currentView, setCurrentView] = useState(PICK_SESSION); //Defines in which modal we are + const currentSession = sessions.find(s => s.id === currentSessionId) || null; useEffect(() => { if (isOpen) { - if (currentSessionId !== null && sessions.find(s => s.id === currentSessionId)) { + if (currentSession !== null) { setCurrentView(SESSION); } else { setCurrentView(PICK_SESSION); } } - }, [isOpen, currentSessionId, sessions]); - - const currentSession = sessions.find(s => s.id === currentSessionId) || null; + }, [isOpen, currentSessionId]); const handleStartSession = (sessionId) => { - setcurrentSessionId(sessionId); setCurrentView(SESSION); }; const handleCreateSession = () => { //Dummy function to create a session... - const newSession = { - id: generateUniqueId(), - name: Math.random().toString(36).substr(2, 9), - lastEdited: new Date().toLocaleDateString(), - lifeSpan: 30, - currentUser: 'TheCreator', - link: `https://collab.app/session/${Date.now().toString()}`, - participants: ['TheCreator'], - }; - setSessions(prevSessions => [...prevSessions, newSession]); - setcurrentSessionId(newSession.id); - setCurrentView(SESSION); + sessionsSocket.connect(); + + sessionsSocket.on('connected', data => { + const newSession = { + id: generateUniqueId(), + name: Math.random().toString(36).substr(2, 9), + lastEdited: new Date().toLocaleDateString(), + lifeSpan: 30, + currentUser: 'TheCreator', + link: `https://collab.app/session/${data['room_id']}`, + participants: ['TheCreator'], + } + + setCurrentSessionId(newSession.id); + setSessions(prevSessions => [...prevSessions, newSession]); + setCurrentView(SESSION); + }); }; const handleExitSession = () => { - setcurrentSessionId(null); + sessionsSocket.disconnect(); setCurrentView(PICK_SESSION); }; @@ -78,6 +82,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { ); } }; + return ( diff --git a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx index a35d423f..6d50ad7c 100644 --- a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx +++ b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx @@ -31,13 +31,13 @@ const dummySessions: CollabSession[] = [ const CollaborativeSession = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [sessions, setSessions] = useState(dummySessions); - const [currentSessionId, setcurrentSessionId] = useState(null); + const [currentSessionId, setCurrentSessionId] = useState(null); const openModal = () => setIsModalOpen(true); const closeModal = () => setIsModalOpen(false); return ( - +
diff --git a/src/index.tsx b/src/index.tsx index 47acd1b2..81a8fedf 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,34 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' +import './api/socket' +import { sessionsSocket } from './api/socket' const strictMode = false const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) +// sessionsSocket.connect(); + +// sessionsSocket.on('connected', data => { +// console.log('Connected to sessions socket'); +// console.log(data); +// }); + +// sessionsSocket.onAny((event, data) => { +// console.log('Received event from sessions socket'); +// console.log(event); +// console.log(data); +// }) + +// sessionsSocket.on('disconnect', data => { +// console.log('Disconnected from sessions socket'); +// console.log(data); +// }); + +// setTimeout(() => { +// sessionsSocket.emit('test', 'Hello, world!'); +// }); + root.render(strictMode ? From 33a31218ca38b3469eaecaffb00edf1aac4b9ec4 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Mon, 30 Dec 2024 14:02:50 +0000 Subject: [PATCH 04/22] Allow connection to session using URL --- .../sidebar/sessionController/CollabModal.tsx | 43 ++++++++++++------- .../CollaborativeSession.tsx | 2 +- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 153a61bc..333bb3d0 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -6,9 +6,8 @@ import CollabSessionModal from './CollabSessionModal'; import CollabSessionContext from '../../../../contexts/CollabSessionContext'; import { sessionsSocket } from '../../../../api/socket'; import { toast } from '../../../ui/use-toast'; +import { useSearchParams } from 'react-router-dom'; -const PICK_SESSION = 'PICK_SESSION'; -const SESSION = 'SESSION'; const generateUniqueId = () => Date.now(); type Props = { @@ -18,21 +17,35 @@ type Props = { const CollabModal = ({ isOpen, closeModal }: Props) => { const { sessions, setSessions, currentSessionId, setCurrentSessionId } = useContext(CollabSessionContext); - const [currentView, setCurrentView] = useState(PICK_SESSION); //Defines in which modal we are const currentSession = sessions.find(s => s.id === currentSessionId) || null; + const [searchParams, _] = useSearchParams(); useEffect(() => { - if (isOpen) { - if (currentSession !== null) { - setCurrentView(SESSION); - } else { - setCurrentView(PICK_SESSION); - } + if (searchParams.has('session')) { + const sessionId = parseInt(searchParams.get('session')!); + handleStartSession(sessionId); } - }, [isOpen, currentSessionId]); + }, []); const handleStartSession = (sessionId) => { - setCurrentView(SESSION); + sessionsSocket.connect(); + + sessionsSocket.on('connected', data => { + const newSession = { + id: data['room_id'], + name: Math.random().toString(36).substr(2, 9), + lastEdited: new Date().toLocaleDateString(), + lifeSpan: 30, + currentUser: 'TheCreator', + link: `http://localhost:3100/planner?session=${data['room_id']}`, + participants: ['TheCreator'], + } + + setCurrentSessionId(newSession.id); + setSessions(prevSessions => [...prevSessions, newSession]); + + toast({ title: 'Sessão criada', description: 'Convida mais amigos para se juntarem!'}); + }); }; const handleCreateSession = () => { //Dummy function to create a session... @@ -45,13 +58,12 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { lastEdited: new Date().toLocaleDateString(), lifeSpan: 30, currentUser: 'TheCreator', - link: `https://collab.app/session/${data['room_id']}`, + link: `http://localhost:3100/planner?session=${data['room_id']}`, participants: ['TheCreator'], } setCurrentSessionId(newSession.id); setSessions(prevSessions => [...prevSessions, newSession]); - setCurrentView(SESSION); toast({ title: 'Sessão criada', description: 'Convida mais amigos para se juntarem!'}); }); @@ -61,7 +73,6 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { sessionsSocket.disconnect(); toast({ title: 'Sessão abandonada', description: 'Podes voltar a ela mais tarde, ou iniciar/entrar noutra sessão.'}); setCurrentSessionId(null); - setCurrentView(PICK_SESSION); }; const handleDeleteSession = (sessionId: number | null) => { @@ -125,7 +136,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => {
- {currentView === PICK_SESSION && ( + {currentSessionId === null && ( { /> )} - {currentView === SESSION && currentSession && ( + {currentSessionId !== null && ( Date: Mon, 30 Dec 2024 14:42:10 +0000 Subject: [PATCH 05/22] Make join with link connect to right room --- src/api/socket.ts | 10 +++++++++- .../planner/sidebar/sessionController/CollabModal.tsx | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/api/socket.ts b/src/api/socket.ts index e27324ae..d2c5b562 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -40,13 +40,21 @@ class SessionsSocket { return this._roomId; } - connect() { + connect(room_id?: string) { const newSocket = io(this.url, { + ...room_id ? { query: { room_id: room_id } } : {}, auth: { token: 'dummy', // TODO: Replace with actual federated authentication token } }); + console.log({ + ...room_id ? { query: { room_id: room_id } } : {}, + auth: { + token: 'dummy', // TODO: Replace with actual federated authentication token + } + }) + this.socket.set(newSocket); newSocket.on('connected', data => { console.log('Connected to sessions socket'); diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 333bb3d0..0c4c037a 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -22,13 +22,13 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { useEffect(() => { if (searchParams.has('session')) { - const sessionId = parseInt(searchParams.get('session')!); + const sessionId = searchParams.get('session')!; handleStartSession(sessionId); } }, []); const handleStartSession = (sessionId) => { - sessionsSocket.connect(); + sessionsSocket.connect(sessionId); sessionsSocket.on('connected', data => { const newSession = { From 982cb686793e6941ab929cbe7a5123e3e1dc15aa Mon Sep 17 00:00:00 2001 From: Process-ing Date: Thu, 2 Jan 2025 11:24:35 +0000 Subject: [PATCH 06/22] Trasmit pings through the room Co-authored-by: Rodrigo Coelho --- src/api/socket.ts | 7 ------ .../sidebar/sessionController/CollabModal.tsx | 23 ++++++++++++++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/api/socket.ts b/src/api/socket.ts index d2c5b562..8c836a83 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -48,13 +48,6 @@ class SessionsSocket { } }); - console.log({ - ...room_id ? { query: { room_id: room_id } } : {}, - auth: { - token: 'dummy', // TODO: Replace with actual federated authentication token - } - }) - this.socket.set(newSocket); newSocket.on('connected', data => { console.log('Connected to sessions socket'); diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 0c4c037a..2cc684ce 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -18,7 +18,7 @@ type Props = { const CollabModal = ({ isOpen, closeModal }: Props) => { const { sessions, setSessions, currentSessionId, setCurrentSessionId } = useContext(CollabSessionContext); const currentSession = sessions.find(s => s.id === currentSessionId) || null; - const [searchParams, _] = useSearchParams(); + const [searchParams, ] = useSearchParams(); useEffect(() => { if (searchParams.has('session')) { @@ -27,6 +27,27 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { } }, []); + // TODO: Remove this + let interval: number | null = null; + const [uid, ] = useState(generateUniqueId()); + useEffect(() => { + if (!currentSessionId) { + if (interval) + clearInterval(interval!); + interval = null; + return; + } + + sessionsSocket.on('ping', data => { + console.log('Received ping', data['id']); + }); + + interval = setInterval(() => { + sessionsSocket.emit('ping', { 'id': uid, 'room_id': sessionsSocket.roomId }); + console.log('Sent ping', uid); + }, 1000); + }, [currentSessionId]); + const handleStartSession = (sessionId) => { sessionsSocket.connect(sessionId); From 3578660e4b1e42fe4fc2376be9a3df495a804c16 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Thu, 2 Jan 2025 11:35:28 +0000 Subject: [PATCH 07/22] Refactor "room" variables to "session" variables --- src/api/socket.ts | 12 ++++++------ .../sidebar/sessionController/CollabModal.tsx | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/api/socket.ts b/src/api/socket.ts index 8c836a83..33f03e82 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -29,20 +29,20 @@ class OptionalSocket { class SessionsSocket { private url: string; private socket: OptionalSocket; - private _roomId: number | null; + private _sessionId: number | null; constructor(url: string) { this.url = url; this.socket = new OptionalSocket(); } - get roomId() { - return this._roomId; + get sessionId() { + return this._sessionId; } - connect(room_id?: string) { + connect(sessionId?: string) { const newSocket = io(this.url, { - ...room_id ? { query: { room_id: room_id } } : {}, + ...sessionId ? { query: { session_id: sessionId } } : {}, auth: { token: 'dummy', // TODO: Replace with actual federated authentication token } @@ -51,7 +51,7 @@ class SessionsSocket { this.socket.set(newSocket); newSocket.on('connected', data => { console.log('Connected to sessions socket'); - this._roomId = data['room_id']; + this._sessionId = data['session_id']; }); } diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 2cc684ce..622aaaad 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -43,7 +43,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { }); interval = setInterval(() => { - sessionsSocket.emit('ping', { 'id': uid, 'room_id': sessionsSocket.roomId }); + sessionsSocket.emit('ping', { 'id': uid, 'session_id': sessionsSocket.sessionId }); console.log('Sent ping', uid); }, 1000); }, [currentSessionId]); @@ -53,12 +53,12 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { sessionsSocket.on('connected', data => { const newSession = { - id: data['room_id'], + id: data['session_id'], name: Math.random().toString(36).substr(2, 9), lastEdited: new Date().toLocaleDateString(), lifeSpan: 30, currentUser: 'TheCreator', - link: `http://localhost:3100/planner?session=${data['room_id']}`, + link: `http://localhost:3100/planner?session=${data['session_id']}`, participants: ['TheCreator'], } @@ -74,12 +74,12 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { sessionsSocket.on('connected', data => { const newSession = { - id: generateUniqueId(), + id: data['session_id'], name: Math.random().toString(36).substr(2, 9), lastEdited: new Date().toLocaleDateString(), lifeSpan: 30, currentUser: 'TheCreator', - link: `http://localhost:3100/planner?session=${data['room_id']}`, + link: `http://localhost:3100/planner?session=${data['session_id']}`, participants: ['TheCreator'], } From 28ec447f855a8b999e885333050f3f15165ba727 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Thu, 2 Jan 2025 12:38:46 +0000 Subject: [PATCH 08/22] Handle connection errors --- src/api/socket.ts | 41 ++++++++++++------- .../sidebar/sessionController/CollabModal.tsx | 8 +++- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/api/socket.ts b/src/api/socket.ts index 33f03e82..856e016f 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -40,24 +40,35 @@ class SessionsSocket { return this._sessionId; } - connect(sessionId?: string) { - const newSocket = io(this.url, { - ...sessionId ? { query: { session_id: sessionId } } : {}, - auth: { - token: 'dummy', // TODO: Replace with actual federated authentication token - } - }); - - this.socket.set(newSocket); - newSocket.on('connected', data => { - console.log('Connected to sessions socket'); - this._sessionId = data['session_id']; + async connect(sessionId?: string): Promise { + return new Promise((resolve, reject) => { + const newSocket = io(this.url, { + ...sessionId ? { query: { session_id: sessionId } } : {}, + auth: { + token: 'dummy', // TODO: Replace with actual federated authentication token + } + }); + + this.socket.set(newSocket); + + newSocket.on('connected', data => { + this._sessionId = data['session_id']; + resolve(); + }); + + newSocket.on('connect_error', (err) => { + this.socket.unset(); + reject(err); + }); }); } - disconnect() { - this.socket.use(socket => socket.disconnect()); - this.socket.unset(); + async disconnect(): Promise { + return new Promise(resolve => { + this.socket.use(socket => socket.disconnect()); + this.socket.unset(); + resolve(); + }); } on(event: string, callback: (...args: any[]) => void) { diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 622aaaad..9cdbd3d1 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -49,7 +49,9 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { }, [currentSessionId]); const handleStartSession = (sessionId) => { - sessionsSocket.connect(sessionId); + sessionsSocket.connect(sessionId) + .catch(err => toast({ title: 'Erro ao entrar na sessão', description: 'Tente novamente mais tarde.' })); + sessionsSocket.on('connected', data => { const newSession = { @@ -70,7 +72,9 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { }; const handleCreateSession = () => { //Dummy function to create a session... - sessionsSocket.connect(); + sessionsSocket.connect() + .catch(err => toast({ title: 'Erro ao entrar na sessão', description: 'Tente novamente mais tarde.' })); + sessionsSocket.on('connected', data => { const newSession = { From 2ceb12904a976bcdccdd921bec9edc9f1e41f4d4 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Thu, 2 Jan 2025 13:12:28 +0000 Subject: [PATCH 09/22] Add unnexpected discconect handles Co-authored-by: Rodrigo Coelho --- .../sidebar/sessionController/CollabModal.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 9cdbd3d1..ab4fa84d 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -47,6 +47,11 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { console.log('Sent ping', uid); }, 1000); }, [currentSessionId]); + + const handleUnexpectedDisconnect = () => { + setCurrentSessionId(null); + toast({ title: 'Foste desconectado inesperadamente', description: 'Por favor, tenta novamente mais tarde.' }); + }; const handleStartSession = (sessionId) => { sessionsSocket.connect(sessionId) @@ -67,13 +72,15 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { setCurrentSessionId(newSession.id); setSessions(prevSessions => [...prevSessions, newSession]); - toast({ title: 'Sessão criada', description: 'Convida mais amigos para se juntarem!'}); + toast({ title: 'Entrou na sessão', description: 'Convida mais amigos para se juntarem!'}); }); + + sessionsSocket.on('disconnect', handleUnexpectedDisconnect); }; const handleCreateSession = () => { //Dummy function to create a session... sessionsSocket.connect() - .catch(err => toast({ title: 'Erro ao entrar na sessão', description: 'Tente novamente mais tarde.' })); + .catch(err => toast({ title: 'Erro ao criar a sessão', description: 'Tente novamente mais tarde.' })); sessionsSocket.on('connected', data => { @@ -92,9 +99,12 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { toast({ title: 'Sessão criada', description: 'Convida mais amigos para se juntarem!'}); }); + + sessionsSocket.on('disconnect', handleUnexpectedDisconnect); }; const handleExitSession = () => { + sessionsSocket.off('disconnect', handleUnexpectedDisconnect); sessionsSocket.disconnect(); toast({ title: 'Sessão abandonada', description: 'Podes voltar a ela mais tarde, ou iniciar/entrar noutra sessão.'}); setCurrentSessionId(null); From 20dbd936563678fc751e845175f86b698296d87c Mon Sep 17 00:00:00 2001 From: Process-ing Date: Thu, 2 Jan 2025 13:41:55 +0000 Subject: [PATCH 10/22] Implement emitToSession --- src/@types/index.d.ts | 2 +- src/api/socket.ts | 21 ++++++++++++++++-- .../sidebar/sessionController/CollabModal.tsx | 7 +++--- .../sessionController/CollabPickSession.tsx | 4 ++-- .../CollaborativeSession.tsx | 6 ++--- src/contexts/CollabSessionContext.tsx | 4 ++-- src/index.tsx | 22 ------------------- 7 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index f19e0c40..d2f957ec 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -162,7 +162,7 @@ export type Student = { } export type CollabSession = { - id: number + id: string name: string lastEdited: string lifeSpan: number diff --git a/src/api/socket.ts b/src/api/socket.ts index 856e016f..92d0b7ed 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -18,6 +18,10 @@ class OptionalSocket { this.socket = null; } + is_set() { + return this.socket !== null + } + use(callback: (socket: Socket) => T): T { if (!this.socket) { throw new Error('Socket is not connected'); @@ -34,16 +38,25 @@ class SessionsSocket { constructor(url: string) { this.url = url; this.socket = new OptionalSocket(); + this._sessionId = null; } get sessionId() { return this._sessionId; } - async connect(sessionId?: string): Promise { + set sessionId(sessionId: number | null) { + this._sessionId = sessionId; + } + + isConnected() { + this.socket.is_set(); + } + + async connect(): Promise { return new Promise((resolve, reject) => { const newSocket = io(this.url, { - ...sessionId ? { query: { session_id: sessionId } } : {}, + ...this.sessionId ? { query: { session_id: this.sessionId } } : {}, auth: { token: 'dummy', // TODO: Replace with actual federated authentication token } @@ -86,6 +99,10 @@ class SessionsSocket { emit(event: string, ...args: any[]) { this.socket.use(socket => socket.emit(event, args)); } + + emitToSession(event: string, session_id: string, ...args: any) { + this.socket.use(socket => socket.emit(event, session_id=session_id, ...args)); + } } const sessionsSocket = new SessionsSocket(SOCKET_URL); diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index ab4fa84d..7bb68444 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -43,7 +43,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { }); interval = setInterval(() => { - sessionsSocket.emit('ping', { 'id': uid, 'session_id': sessionsSocket.sessionId }); + sessionsSocket.emitToSession('ping', currentSessionId, { 'id': uid }); console.log('Sent ping', uid); }, 1000); }, [currentSessionId]); @@ -54,7 +54,8 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { }; const handleStartSession = (sessionId) => { - sessionsSocket.connect(sessionId) + sessionsSocket.sessionId = sessionId; + sessionsSocket.connect() .catch(err => toast({ title: 'Erro ao entrar na sessão', description: 'Tente novamente mais tarde.' })); @@ -110,7 +111,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { setCurrentSessionId(null); }; - const handleDeleteSession = (sessionId: number | null) => { + const handleDeleteSession = (sessionId: string | null) => { setSessions(prevSessions => prevSessions.filter(session => session.id !== sessionId)); if (currentSession?.id === sessionId) { handleExitSession(); diff --git a/src/components/planner/sidebar/sessionController/CollabPickSession.tsx b/src/components/planner/sidebar/sessionController/CollabPickSession.tsx index c0e2ef46..5a602c3a 100644 --- a/src/components/planner/sidebar/sessionController/CollabPickSession.tsx +++ b/src/components/planner/sidebar/sessionController/CollabPickSession.tsx @@ -5,9 +5,9 @@ import { CollabSession } from '../../../../@types'; type Props = { sessions: Array, - onStartSession: (arg: number | null) => void + onStartSession: (arg: string | null) => void onCreateSession: () => void - onDeleteSession: (arg: number | null) => void + onDeleteSession: (arg: string | null) => void } const CollabPickSession = ({ sessions, onStartSession, onCreateSession, onDeleteSession }: Props) => ( diff --git a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx index 4f61e3e3..49352424 100644 --- a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx +++ b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx @@ -9,7 +9,7 @@ import { CollabSession } from '../../../../@types'; // dummySessions are just dummy default sessions to help visualize them until we actually have sessions where participants can join and stuff... const dummySessions: CollabSession[] = [ { - id: 1, + id: '1', name: 'asdipuhaosd', lastEdited: 'há 3 dias', currentUser: 'Jota Mongoose', @@ -18,7 +18,7 @@ const dummySessions: CollabSession[] = [ participants: ['Jota Mongoose','Duarte', 'Olivia', 'Ricardo', 'Miguel', 'João', 'Mariana', 'Ana'] }, { - id: 2, + id: '2', name: 'uyavfiuya8gf3', lastEdited: 'há 1 semana', currentUser: 'msantos', @@ -31,7 +31,7 @@ const dummySessions: CollabSession[] = [ const CollaborativeSession = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [sessions, setSessions] = useState(dummySessions); - const [currentSessionId, setCurrentSessionId] = useState(null); + const [currentSessionId, setCurrentSessionId] = useState(null); const openModal = () => setIsModalOpen(true); const closeModal = () => setIsModalOpen(false); diff --git a/src/contexts/CollabSessionContext.tsx b/src/contexts/CollabSessionContext.tsx index 9e6e8b0a..92f988bf 100644 --- a/src/contexts/CollabSessionContext.tsx +++ b/src/contexts/CollabSessionContext.tsx @@ -4,8 +4,8 @@ import { CollabSession } from '../@types'; interface CollabSessionContextContent { sessions: CollabSession[]; setSessions: Dispatch>; - currentSessionId: number | null; // Use index instead of the full session object - setCurrentSessionId: Dispatch>; + currentSessionId: string | null; // Use index instead of the full session object + setCurrentSessionId: Dispatch>; } const CollabSessionContext: Context = createContext({ diff --git a/src/index.tsx b/src/index.tsx index 81a8fedf..7803513a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,28 +7,6 @@ import { sessionsSocket } from './api/socket' const strictMode = false const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) -// sessionsSocket.connect(); - -// sessionsSocket.on('connected', data => { -// console.log('Connected to sessions socket'); -// console.log(data); -// }); - -// sessionsSocket.onAny((event, data) => { -// console.log('Received event from sessions socket'); -// console.log(event); -// console.log(data); -// }) - -// sessionsSocket.on('disconnect', data => { -// console.log('Disconnected from sessions socket'); -// console.log(data); -// }); - -// setTimeout(() => { -// sessionsSocket.emit('test', 'Hello, world!'); -// }); - root.render(strictMode ? From fd73981bb5643cfd8e7f2e20d6eea6ba9cf33cf6 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Thu, 2 Jan 2025 15:38:37 +0000 Subject: [PATCH 11/22] Fetch session info from backend Co-authored-by: Rodrigo Coelho --- src/api/socket.ts | 25 ++++++-- .../sidebar/sessionController/CollabModal.tsx | 62 +++++++++---------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/src/api/socket.ts b/src/api/socket.ts index 92d0b7ed..25569b6b 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -33,39 +33,52 @@ class OptionalSocket { class SessionsSocket { private url: string; private socket: OptionalSocket; - private _sessionId: number | null; + private _sessionId: string; + private _sessionInfo: any; constructor(url: string) { this.url = url; this.socket = new OptionalSocket(); this._sessionId = null; + this._sessionInfo = null; } - get sessionId() { + get sessionId(): string | null { return this._sessionId; } - set sessionId(sessionId: number | null) { + set sessionId(sessionId: string | null) { this._sessionId = sessionId; } + get sessionInfo() { + return this._sessionInfo; + } + isConnected() { this.socket.is_set(); } - async connect(): Promise { + async connect(participantName: string): Promise { return new Promise((resolve, reject) => { + const query = { + ...(this.sessionId ? { session_id: this.sessionId } : {}), + participant_name: participantName, + }; + const newSocket = io(this.url, { - ...this.sessionId ? { query: { session_id: this.sessionId } } : {}, + query, auth: { token: 'dummy', // TODO: Replace with actual federated authentication token - } + }, }); this.socket.set(newSocket); newSocket.on('connected', data => { this._sessionId = data['session_id']; + this._sessionInfo = data['session_info']; + console.log('Connected to session', this._sessionId); resolve(); }); diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 7bb68444..51839348 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -28,13 +28,13 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { }, []); // TODO: Remove this - let interval: number | null = null; + const [interval, setInt] = useState(null); const [uid, ] = useState(generateUniqueId()); useEffect(() => { if (!currentSessionId) { if (interval) clearInterval(interval!); - interval = null; + setInt(null); return; } @@ -42,10 +42,10 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { console.log('Received ping', data['id']); }); - interval = setInterval(() => { + setInt(setInterval(() => { sessionsSocket.emitToSession('ping', currentSessionId, { 'id': uid }); console.log('Sent ping', uid); - }, 1000); + }, 1000)); }, [currentSessionId]); const handleUnexpectedDisconnect = () => { @@ -55,22 +55,22 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { const handleStartSession = (sessionId) => { sessionsSocket.sessionId = sessionId; - sessionsSocket.connect() + sessionsSocket.connect('TheCreator') .catch(err => toast({ title: 'Erro ao entrar na sessão', description: 'Tente novamente mais tarde.' })); - sessionsSocket.on('connected', data => { + sessionsSocket.on('connect', () => { const newSession = { - id: data['session_id'], + id: sessionsSocket.sessionId, name: Math.random().toString(36).substr(2, 9), lastEdited: new Date().toLocaleDateString(), lifeSpan: 30, currentUser: 'TheCreator', - link: `http://localhost:3100/planner?session=${data['session_id']}`, - participants: ['TheCreator'], + link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, + participants: sessionsSocket.sessionInfo['participants'], } - setCurrentSessionId(newSession.id); + setCurrentSessionId(sessionsSocket.sessionId); setSessions(prevSessions => [...prevSessions, newSession]); toast({ title: 'Entrou na sessão', description: 'Convida mais amigos para se juntarem!'}); @@ -80,28 +80,28 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { }; const handleCreateSession = () => { //Dummy function to create a session... - sessionsSocket.connect() + sessionsSocket.connect('TheCreator') + .then(() => { + console.log((sessionsSocket.sessionInfo['participants'])); + + const newSession = { + id: sessionsSocket.sessionId, + name: Math.random().toString(36).substr(2, 9), + lastEdited: new Date().toLocaleDateString(), + lifeSpan: 30, + currentUser: 'TheCreator', + link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, + participants: (sessionsSocket.sessionInfo['participants']).map(participant => participant['name']), + }; + + setCurrentSessionId(sessionsSocket.sessionId); + setSessions(prevSessions => [...prevSessions, newSession]); + + toast({ title: 'Sessão criada', description: 'Convida mais amigos para se juntarem!'}); + + sessionsSocket.on('disconnect', handleUnexpectedDisconnect); + }) .catch(err => toast({ title: 'Erro ao criar a sessão', description: 'Tente novamente mais tarde.' })); - - - sessionsSocket.on('connected', data => { - const newSession = { - id: data['session_id'], - name: Math.random().toString(36).substr(2, 9), - lastEdited: new Date().toLocaleDateString(), - lifeSpan: 30, - currentUser: 'TheCreator', - link: `http://localhost:3100/planner?session=${data['session_id']}`, - participants: ['TheCreator'], - } - - setCurrentSessionId(newSession.id); - setSessions(prevSessions => [...prevSessions, newSession]); - - toast({ title: 'Sessão criada', description: 'Convida mais amigos para se juntarem!'}); - }); - - sessionsSocket.on('disconnect', handleUnexpectedDisconnect); }; const handleExitSession = () => { From ba9fb0528c4dae368e1149481c8cf4f842afe8af Mon Sep 17 00:00:00 2001 From: Process-ing Date: Thu, 2 Jan 2025 16:08:56 +0000 Subject: [PATCH 12/22] Update session info on demand --- src/api/socket.ts | 4 +- .../sidebar/sessionController/CollabModal.tsx | 58 +++++++++++-------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/api/socket.ts b/src/api/socket.ts index 25569b6b..6aa75098 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -59,7 +59,7 @@ class SessionsSocket { this.socket.is_set(); } - async connect(participantName: string): Promise { + async connect(participantName: string): Promise { return new Promise((resolve, reject) => { const query = { ...(this.sessionId ? { session_id: this.sessionId } : {}), @@ -79,7 +79,7 @@ class SessionsSocket { this._sessionId = data['session_id']; this._sessionInfo = data['session_info']; console.log('Connected to session', this._sessionId); - resolve(); + resolve(this); }); newSocket.on('connect_error', (err) => { diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 51839348..bbcd0522 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -48,40 +48,53 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { }, 1000)); }, [currentSessionId]); + + const updatedSession = (sessionId: string, sessionInfo: any) => { + setSessions(prevSessions => + prevSessions.map(session => + session.id === sessionId ? { ...session, participants: sessionInfo['participants'].map((participant: any) => participant['name']) } : session + ) + ); + } + const handleUnexpectedDisconnect = () => { setCurrentSessionId(null); toast({ title: 'Foste desconectado inesperadamente', description: 'Por favor, tenta novamente mais tarde.' }); }; + + const addSocketListeners = socket => { + socket.on('disconnect', handleUnexpectedDisconnect); + socket.on('update_session_info', (data) => updatedSession(data['session_id'], data['session_info'])); + }; + const handleStartSession = (sessionId) => { sessionsSocket.sessionId = sessionId; sessionsSocket.connect('TheCreator') + .then(sessionsSocket => { + const newSession = { + id: sessionsSocket.sessionId, + name: Math.random().toString(36).substr(2, 9), + lastEdited: new Date().toLocaleDateString(), + lifeSpan: 30, + currentUser: 'TheCreator', + link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, + participants: (sessionsSocket.sessionInfo['participants']).map(participant => participant['name']), + } + + addSocketListeners(sessionsSocket); + setCurrentSessionId(sessionsSocket.sessionId); + setSessions(prevSessions => [...prevSessions, newSession]); + + toast({ title: 'Entrou na sessão', description: 'Convida mais amigos para se juntarem!'}); + }) .catch(err => toast({ title: 'Erro ao entrar na sessão', description: 'Tente novamente mais tarde.' })); - - - sessionsSocket.on('connect', () => { - const newSession = { - id: sessionsSocket.sessionId, - name: Math.random().toString(36).substr(2, 9), - lastEdited: new Date().toLocaleDateString(), - lifeSpan: 30, - currentUser: 'TheCreator', - link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, - participants: sessionsSocket.sessionInfo['participants'], - } - - setCurrentSessionId(sessionsSocket.sessionId); - setSessions(prevSessions => [...prevSessions, newSession]); - - toast({ title: 'Entrou na sessão', description: 'Convida mais amigos para se juntarem!'}); - }); - - sessionsSocket.on('disconnect', handleUnexpectedDisconnect); }; const handleCreateSession = () => { //Dummy function to create a session... + sessionsSocket.sessionId = null; sessionsSocket.connect('TheCreator') - .then(() => { + .then(sessionsSocket => { console.log((sessionsSocket.sessionInfo['participants'])); const newSession = { @@ -94,12 +107,11 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { participants: (sessionsSocket.sessionInfo['participants']).map(participant => participant['name']), }; + addSocketListeners(sessionsSocket); setCurrentSessionId(sessionsSocket.sessionId); setSessions(prevSessions => [...prevSessions, newSession]); toast({ title: 'Sessão criada', description: 'Convida mais amigos para se juntarem!'}); - - sessionsSocket.on('disconnect', handleUnexpectedDisconnect); }) .catch(err => toast({ title: 'Erro ao criar a sessão', description: 'Tente novamente mais tarde.' })); }; From fc598341257eb969445a90bb130e83a8d7a46c93 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Fri, 3 Jan 2025 10:50:10 +0000 Subject: [PATCH 13/22] Assign ids to participants --- src/@types/index.d.ts | 10 ++++- src/api/socket.ts | 15 ++++--- .../sidebar/sessionController/CollabModal.tsx | 39 ++++++++++--------- .../sessionController/CollabSessionModal.tsx | 17 ++++---- .../CollaborativeSession.tsx | 14 ++++--- 5 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index d2f957ec..56de5bab 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -161,12 +161,18 @@ export type Student = { mecNumber: number } +export type Participant = { + client_id: string + name: string +} + +// TODO(Process-ing): Maybe join Student and Participant into a single type + export type CollabSession = { id: string name: string lastEdited: string lifeSpan: number - currentUser: string link: string - participants: Array + participants: Array } diff --git a/src/api/socket.ts b/src/api/socket.ts index 6aa75098..51119c59 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -33,12 +33,14 @@ class OptionalSocket { class SessionsSocket { private url: string; private socket: OptionalSocket; - private _sessionId: string; + private _clientId: string | null; + private _sessionId: string | null; private _sessionInfo: any; constructor(url: string) { this.url = url; this.socket = new OptionalSocket(); + this._clientId = null; this._sessionId = null; this._sessionInfo = null; } @@ -47,6 +49,10 @@ class SessionsSocket { return this._sessionId; } + get clientId(): string | null { + return this._clientId; + } + set sessionId(sessionId: string | null) { this._sessionId = sessionId; } @@ -76,6 +82,7 @@ class SessionsSocket { this.socket.set(newSocket); newSocket.on('connected', data => { + this._clientId = data['client_id']; this._sessionId = data['session_id']; this._sessionInfo = data['session_info']; console.log('Connected to session', this._sessionId); @@ -110,11 +117,7 @@ class SessionsSocket { } emit(event: string, ...args: any[]) { - this.socket.use(socket => socket.emit(event, args)); - } - - emitToSession(event: string, session_id: string, ...args: any) { - this.socket.use(socket => socket.emit(event, session_id=session_id, ...args)); + this.socket.use(socket => socket.emit(event, ...args)); } } diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index bbcd0522..97df6f31 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -43,7 +43,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { }); setInt(setInterval(() => { - sessionsSocket.emitToSession('ping', currentSessionId, { 'id': uid }); + sessionsSocket.emit('ping', { id: uid }); console.log('Sent ping', uid); }, 1000)); }, [currentSessionId]); @@ -79,7 +79,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { lifeSpan: 30, currentUser: 'TheCreator', link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, - participants: (sessionsSocket.sessionInfo['participants']).map(participant => participant['name']), + participants: sessionsSocket.sessionInfo['participants'], } addSocketListeners(sessionsSocket); @@ -102,9 +102,8 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { name: Math.random().toString(36).substr(2, 9), lastEdited: new Date().toLocaleDateString(), lifeSpan: 30, - currentUser: 'TheCreator', link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, - participants: (sessionsSocket.sessionInfo['participants']).map(participant => participant['name']), + participants: sessionsSocket.sessionInfo['participants'], }; addSocketListeners(sessionsSocket); @@ -130,21 +129,23 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { } }; - const handleUpdateUser = (updatedUser: string) => { - if (currentSession) { - const updatedSession = { - ...currentSession, - currentUser: updatedUser, - participants: currentSession.participants.map(participant => - participant === currentSession.currentUser ? updatedUser : participant - ) - }; - setSessions(prevSessions => - prevSessions.map(session => - session.id === currentSession.id ? updatedSession : session - ) - ); - } + const handleUpdateUser = (updatedName: string) => { + if (!currentSession) + return; + + const updatedSession = { + ...currentSession, + participants: currentSession.participants.map(participant => + participant.client_id === sessionsSocket.clientId ? { ...participant, name: updatedName } : participant + ) + }; + setSessions(prevSessions => + prevSessions.map(session => + session.id === currentSession.id ? updatedSession : session + ) + ); + + sessionsSocket.emit('update_user', { 'name': updatedName }); }; return ( diff --git a/src/components/planner/sidebar/sessionController/CollabSessionModal.tsx b/src/components/planner/sidebar/sessionController/CollabSessionModal.tsx index d30eebbc..e4316bfc 100644 --- a/src/components/planner/sidebar/sessionController/CollabSessionModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabSessionModal.tsx @@ -4,6 +4,7 @@ import { StopIcon } from '@heroicons/react/24/solid'; import { Button } from '../../../ui/button'; import { useToast } from '../../../ui/use-toast'; import { CollabSession } from '../../../../@types'; +import { sessionsSocket } from '../../../../api/socket'; const pastelColors = [ //Colors for the participants 'bg-orange-200 text-orange-700', @@ -22,7 +23,7 @@ type Props = { const CollabSessionModal = ({ session, onExitSession, onUpdateUser }: Props) => { const { toast } = useToast(); const [copied, setCopied] = useState(false); - const [lastValidUser, setLastValidUser] = useState(session.currentUser); + const [lastValidUser, setLastValidUser] = useState(session.participants.find(p => p.client_id === sessionsSocket.clientId)?.name ?? ''); const handleCopyLink = () => { navigator.clipboard.writeText(session.link); @@ -52,6 +53,8 @@ const CollabSessionModal = ({ session, onExitSession, onUpdateUser }: Props) => } }; + const currentUserName = session.participants.find(p => p.client_id === sessionsSocket.clientId)?.name ?? ''; + return (

Colaboração ao vivo...

@@ -60,7 +63,7 @@ const CollabSessionModal = ({ session, onExitSession, onUpdateUser }: Props) => className={`rounded-full h-10 w-10 flex items-center justify-center ${pastelColors[index % pastelColors.length] }`} > - {user[0]} + {user.name[0]} +
+
+ {user.name}
- {user && ( -
- {user} -
- )} ))} diff --git a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx index 49352424..d7ec841c 100644 --- a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx +++ b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx @@ -3,28 +3,32 @@ import { Button } from '../../../ui/button'; import { UsersIcon } from '@heroicons/react/24/solid'; import CollabModal from './CollabModal'; import CollabSessionContext from '../../../../contexts/CollabSessionContext'; -import { CollabSession } from '../../../../@types'; +import { CollabSession, Participant } from '../../../../@types'; +const toParticipant = (name: string): Participant => ({ client_id: crypto.randomUUID(), name: name }); // dummySessions are just dummy default sessions to help visualize them until we actually have sessions where participants can join and stuff... +const dummyParticipants = [ + ['Jota Mongoose','Duarte', 'Olivia', 'Ricardo', 'Miguel', 'João', 'Mariana', 'Ana'].map(toParticipant), + ['msantos','Fabio', 'Luisa'].map(toParticipant), +] + const dummySessions: CollabSession[] = [ { id: '1', name: 'asdipuhaosd', lastEdited: 'há 3 dias', - currentUser: 'Jota Mongoose', link: 'https://ni.fe.up.pt/tts/#room=d8750cf5...', lifeSpan: 7, - participants: ['Jota Mongoose','Duarte', 'Olivia', 'Ricardo', 'Miguel', 'João', 'Mariana', 'Ana'] + participants: dummyParticipants[0], }, { id: '2', name: 'uyavfiuya8gf3', lastEdited: 'há 1 semana', - currentUser: 'msantos', link: 'https://ni.fe.up.pt/tts/#room=d8750cf5...', lifeSpan: 14, - participants: [ 'msantos','Fabio', 'Luisa'] + participants: dummyParticipants[1], }, ]; From c254a7ab24b0bd9e62a95a10fa17dd1de0d0106a Mon Sep 17 00:00:00 2001 From: Process-ing Date: Fri, 3 Jan 2025 10:59:06 +0000 Subject: [PATCH 14/22] Allow user update transmission --- .../planner/sidebar/sessionController/CollabModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 97df6f31..7b8fa77d 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -52,7 +52,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { const updatedSession = (sessionId: string, sessionInfo: any) => { setSessions(prevSessions => prevSessions.map(session => - session.id === sessionId ? { ...session, participants: sessionInfo['participants'].map((participant: any) => participant['name']) } : session + session.id === sessionId ? { ...session, participants: sessionInfo['participants'] } : session ) ); } @@ -145,7 +145,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { ) ); - sessionsSocket.emit('update_user', { 'name': updatedName }); + sessionsSocket.emit('update_participant', { 'name': updatedName }); }; return ( From 15104bb81c5b9046cc430bae699f0166e356190d Mon Sep 17 00:00:00 2001 From: Process-ing Date: Sat, 4 Jan 2025 23:47:18 +0000 Subject: [PATCH 15/22] Read expiration time from backend --- src/@types/index.d.ts | 2 +- .../planner/sidebar/sessionController/CollabModal.tsx | 4 ++-- .../sidebar/sessionController/CollaborativeSession.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index 56de5bab..bdd94255 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -172,7 +172,7 @@ export type CollabSession = { id: string name: string lastEdited: string - lifeSpan: number + expirationTime: number link: string participants: Array } diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 7b8fa77d..2aefca37 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -76,7 +76,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { id: sessionsSocket.sessionId, name: Math.random().toString(36).substr(2, 9), lastEdited: new Date().toLocaleDateString(), - lifeSpan: 30, + expirationTime: new Date(sessionsSocket.sessionInfo['expiration_time']).getTime(), currentUser: 'TheCreator', link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, participants: sessionsSocket.sessionInfo['participants'], @@ -101,7 +101,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { id: sessionsSocket.sessionId, name: Math.random().toString(36).substr(2, 9), lastEdited: new Date().toLocaleDateString(), - lifeSpan: 30, + expirationTime: new Date(sessionsSocket.sessionInfo['expiration_time']).getTime(), link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, participants: sessionsSocket.sessionInfo['participants'], }; diff --git a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx index d7ec841c..9ee241a1 100644 --- a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx +++ b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx @@ -19,7 +19,7 @@ const dummySessions: CollabSession[] = [ name: 'asdipuhaosd', lastEdited: 'há 3 dias', link: 'https://ni.fe.up.pt/tts/#room=d8750cf5...', - lifeSpan: 7, + expirationTime: Date.now() + 1000 * 60 * 60 * 24 * 14, participants: dummyParticipants[0], }, { @@ -27,7 +27,7 @@ const dummySessions: CollabSession[] = [ name: 'uyavfiuya8gf3', lastEdited: 'há 1 semana', link: 'https://ni.fe.up.pt/tts/#room=d8750cf5...', - lifeSpan: 14, + expirationTime: Date.now() + 1000 * 60 * 60 * 24 * 7, participants: dummyParticipants[1], }, ]; From e8b21bf1d5907c1a84d088d7df818bf0bc2eefaa Mon Sep 17 00:00:00 2001 From: Process-ing Date: Sat, 4 Jan 2025 23:48:11 +0000 Subject: [PATCH 16/22] Automatically cleanup expired sessions --- .../sessionController/CollaborativeSession.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx index 9ee241a1..e72386ea 100644 --- a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx +++ b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Button } from '../../../ui/button'; import { UsersIcon } from '@heroicons/react/24/solid'; import CollabModal from './CollabModal'; @@ -37,6 +37,15 @@ const CollaborativeSession = () => { const [sessions, setSessions] = useState(dummySessions); const [currentSessionId, setCurrentSessionId] = useState(null); + // TODO: Move this to sessions hook + const sessionIsNotExpired = (session: CollabSession) => { + return session.expirationTime < Date.now(); + }; + + useEffect(() => { + setSessions(sessions.filter(sessionIsNotExpired)); + }, []); + const openModal = () => setIsModalOpen(true); const closeModal = () => setIsModalOpen(false); From f4e6bf8e3e0a75ddc80743ee7210632ffe7d13fd Mon Sep 17 00:00:00 2001 From: Process-ing Date: Thu, 16 Jan 2025 22:04:18 +0000 Subject: [PATCH 17/22] Fix procession of expiration time --- .../planner/sidebar/sessionController/CollabModal.tsx | 4 +--- .../planner/sidebar/sessionController/CollabPickSession.tsx | 2 +- .../sidebar/sessionController/CollaborativeSession.tsx | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 2aefca37..c272a073 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -95,13 +95,11 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { sessionsSocket.sessionId = null; sessionsSocket.connect('TheCreator') .then(sessionsSocket => { - console.log((sessionsSocket.sessionInfo['participants'])); - const newSession = { id: sessionsSocket.sessionId, name: Math.random().toString(36).substr(2, 9), lastEdited: new Date().toLocaleDateString(), - expirationTime: new Date(sessionsSocket.sessionInfo['expiration_time']).getTime(), + expirationTime: sessionsSocket.sessionInfo['expiration_time'], link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, participants: sessionsSocket.sessionInfo['participants'], }; diff --git a/src/components/planner/sidebar/sessionController/CollabPickSession.tsx b/src/components/planner/sidebar/sessionController/CollabPickSession.tsx index 5a602c3a..0492683c 100644 --- a/src/components/planner/sidebar/sessionController/CollabPickSession.tsx +++ b/src/components/planner/sidebar/sessionController/CollabPickSession.tsx @@ -45,7 +45,7 @@ const CollabPickSession = ({ sessions, onStartSession, onCreateSession, onDelete
  • {session.name} editado {session.lastEdited} - expira em {session.lifeSpan} dias + expira em {Math.floor((session.expirationTime - Date.now()) / (1000 * 60 * 60 * 24))} dias
    { // TODO: Move this to sessions hook const sessionIsNotExpired = (session: CollabSession) => { - return session.expirationTime < Date.now(); + return session.expirationTime > Date.now(); }; useEffect(() => { From 6b7502e64bc773c8024f366c6a4f308d7d91cf2e Mon Sep 17 00:00:00 2001 From: Process-ing Date: Thu, 16 Jan 2025 22:20:09 +0000 Subject: [PATCH 18/22] Use local storage for sessions --- .../CollaborativeSession.tsx | 32 ++----------------- src/hooks/useCollabSessions.ts | 10 ++++++ src/hooks/useDarkMode.tsx | 26 +-------------- src/hooks/useLocalStorage.ts | 28 ++++++++++++++++ 4 files changed, 42 insertions(+), 54 deletions(-) create mode 100644 src/hooks/useCollabSessions.ts create mode 100644 src/hooks/useLocalStorage.ts diff --git a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx index 46ca3620..340ad308 100644 --- a/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx +++ b/src/components/planner/sidebar/sessionController/CollaborativeSession.tsx @@ -3,38 +3,12 @@ import { Button } from '../../../ui/button'; import { UsersIcon } from '@heroicons/react/24/solid'; import CollabModal from './CollabModal'; import CollabSessionContext from '../../../../contexts/CollabSessionContext'; -import { CollabSession, Participant } from '../../../../@types'; - -const toParticipant = (name: string): Participant => ({ client_id: crypto.randomUUID(), name: name }); - -// dummySessions are just dummy default sessions to help visualize them until we actually have sessions where participants can join and stuff... -const dummyParticipants = [ - ['Jota Mongoose','Duarte', 'Olivia', 'Ricardo', 'Miguel', 'João', 'Mariana', 'Ana'].map(toParticipant), - ['msantos','Fabio', 'Luisa'].map(toParticipant), -] - -const dummySessions: CollabSession[] = [ - { - id: '1', - name: 'asdipuhaosd', - lastEdited: 'há 3 dias', - link: 'https://ni.fe.up.pt/tts/#room=d8750cf5...', - expirationTime: Date.now() + 1000 * 60 * 60 * 24 * 14, - participants: dummyParticipants[0], - }, - { - id: '2', - name: 'uyavfiuya8gf3', - lastEdited: 'há 1 semana', - link: 'https://ni.fe.up.pt/tts/#room=d8750cf5...', - expirationTime: Date.now() + 1000 * 60 * 60 * 24 * 7, - participants: dummyParticipants[1], - }, -]; +import { CollabSession } from '../../../../@types'; +import useCollabSessions from '../../../../hooks/useCollabSessions'; const CollaborativeSession = () => { const [isModalOpen, setIsModalOpen] = useState(false); - const [sessions, setSessions] = useState(dummySessions); + const [sessions, setSessions] = useCollabSessions(); const [currentSessionId, setCurrentSessionId] = useState(null); // TODO: Move this to sessions hook diff --git a/src/hooks/useCollabSessions.ts b/src/hooks/useCollabSessions.ts new file mode 100644 index 00000000..a6379fea --- /dev/null +++ b/src/hooks/useCollabSessions.ts @@ -0,0 +1,10 @@ +import { CollabSession } from "../@types"; +import useLocalStorage from "./useLocalStorage"; + +const useCollabSessions = () => { + const [sessions, setSessions] = useLocalStorage('collab-sessions', [] as CollabSession[]); + + return [sessions, setSessions]; +} + +export default useCollabSessions; \ No newline at end of file diff --git a/src/hooks/useDarkMode.tsx b/src/hooks/useDarkMode.tsx index 22ac404a..3cf10d50 100644 --- a/src/hooks/useDarkMode.tsx +++ b/src/hooks/useDarkMode.tsx @@ -1,29 +1,5 @@ import { useEffect, useState } from 'react' - -const useLocalStorage = (key: string, initialValue?: any) => { - const [storedValue, setStoredValue] = useState(() => { - try { - const item = window.localStorage.getItem(key) - return item ? JSON.parse(item) : initialValue - } catch (error) { - console.warn(error) - return initialValue - } - }) - - const setValue = (value: any) => { - try { - const valueToStore = value instanceof Function ? value(storedValue) : value - - setStoredValue(valueToStore) - - window.localStorage.setItem(key, JSON.stringify(valueToStore)) - } catch (error) { - console.warn(error) - } - } - return [storedValue, setValue] -} +import useLocalStorage from './useLocalStorage' const useDarkMode = () => { const [enabled, setEnabled] = useLocalStorage('dark-theme') diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 00000000..43225373 --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,28 @@ +import { useState } from "react" + +const useLocalStorage = (key: string, initialValue?: any) => { + const [storedValue, setStoredValue] = useState(() => { + try { + const item = window.localStorage.getItem(key) + return item ? JSON.parse(item) : initialValue + } catch (error) { + console.warn(error) + return initialValue + } + }) + + const setValue = (value: any) => { + try { + const valueToStore = value instanceof Function ? value(storedValue) : value + + setStoredValue(valueToStore) + + window.localStorage.setItem(key, JSON.stringify(valueToStore)) + } catch (error) { + console.warn(error) + } + } + return [storedValue, setValue] + } + +export default useLocalStorage; \ No newline at end of file From b57184a87e3716a308920d2c0197e0008ab18239 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Fri, 17 Jan 2025 12:36:26 +0000 Subject: [PATCH 19/22] Implement human readable time differences --- src/@types/index.d.ts | 2 +- .../sidebar/sessionController/CollabModal.tsx | 4 +-- .../sessionController/CollabPickSession.tsx | 5 ++-- src/utils/human-time.ts | 27 +++++++++++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 src/utils/human-time.ts diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index bdd94255..3168f16d 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -171,7 +171,7 @@ export type Participant = { export type CollabSession = { id: string name: string - lastEdited: string + lastEdited: number expirationTime: number link: string participants: Array diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index c272a073..5abc812d 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -75,7 +75,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { const newSession = { id: sessionsSocket.sessionId, name: Math.random().toString(36).substr(2, 9), - lastEdited: new Date().toLocaleDateString(), + lastEdited: Date.now(), expirationTime: new Date(sessionsSocket.sessionInfo['expiration_time']).getTime(), currentUser: 'TheCreator', link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, @@ -98,7 +98,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { const newSession = { id: sessionsSocket.sessionId, name: Math.random().toString(36).substr(2, 9), - lastEdited: new Date().toLocaleDateString(), + lastEdited: Date.now(), expirationTime: sessionsSocket.sessionInfo['expiration_time'], link: `http://localhost:3100/planner?session=${sessionsSocket.sessionId}`, participants: sessionsSocket.sessionInfo['participants'], diff --git a/src/components/planner/sidebar/sessionController/CollabPickSession.tsx b/src/components/planner/sidebar/sessionController/CollabPickSession.tsx index 0492683c..b60d375d 100644 --- a/src/components/planner/sidebar/sessionController/CollabPickSession.tsx +++ b/src/components/planner/sidebar/sessionController/CollabPickSession.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { PlayCircleIcon, UserGroupIcon } from '@heroicons/react/20/solid'; import { Button } from '../../../ui/button'; import { CollabSession } from '../../../../@types'; +import toHumanReadableTimeDiff from '../../../../utils/human-time'; type Props = { sessions: Array, @@ -44,8 +45,8 @@ const CollabPickSession = ({ sessions, onStartSession, onCreateSession, onDelete {sessions.map((session) => (
  • {session.name} - editado {session.lastEdited} - expira em {Math.floor((session.expirationTime - Date.now()) / (1000 * 60 * 60 * 24))} dias + editado {toHumanReadableTimeDiff(session.lastEdited)} + expira {toHumanReadableTimeDiff(session.expirationTime)}
    { + const timeDiff = Math.abs(time - pivot); + console.log('DB:', pivot, time); + const isPast = time < pivot; + + for (const [modifier, unit] of timeLevels) { + if (timeDiff >= modifier) { + const value = Math.floor(timeDiff / modifier); + return isPast + ? `${value} ${unit} ago` + : `in ${value} ${unit}`; + } + } + + return 'just now'; +} + +export default toHumanReadableTimeDiff; From 4cd9b0ec3ab134d93dd4ce2cc0fb2b49c0be9fc3 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Fri, 17 Jan 2025 15:22:52 +0000 Subject: [PATCH 20/22] Allow human readable time differences in portuguese --- .../sidebar/sessionController/CollabModal.tsx | 2 +- .../sessionController/CollabPickSession.tsx | 1 - src/utils/human-time.ts | 47 ++++++++++++++----- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/components/planner/sidebar/sessionController/CollabModal.tsx b/src/components/planner/sidebar/sessionController/CollabModal.tsx index 5abc812d..5d9a6013 100644 --- a/src/components/planner/sidebar/sessionController/CollabModal.tsx +++ b/src/components/planner/sidebar/sessionController/CollabModal.tsx @@ -172,7 +172,7 @@ const CollabModal = ({ isOpen, closeModal }: Props) => { leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - +