Compare commits
2 Commits
b01b637a22
...
5fbba6cb98
Author | SHA1 | Date |
---|---|---|
qvalentin | 5fbba6cb98 | |
qvalentin | cf1c7625be |
|
@ -1,6 +1,6 @@
|
||||||
cabal-version: 1.12
|
cabal-version: 1.12
|
||||||
|
|
||||||
-- This file has been generated from package.yaml by hpack version 0.35.1.
|
-- This file has been generated from package.yaml by hpack version 0.35.0.
|
||||||
--
|
--
|
||||||
-- see: https://github.com/sol/hpack
|
-- see: https://github.com/sol/hpack
|
||||||
|
|
||||||
|
@ -35,10 +35,12 @@ library
|
||||||
Types.Participant
|
Types.Participant
|
||||||
Types.RoomData
|
Types.RoomData
|
||||||
Types.RoomsState
|
Types.RoomsState
|
||||||
|
Types.User
|
||||||
Types.UsersData
|
Types.UsersData
|
||||||
Types.WebEnv
|
Types.WebEnv
|
||||||
Types.WebSocketMessages.WebSocketMessages
|
Types.WebSocketMessages.WebSocketMessages
|
||||||
WebServer
|
WebServer
|
||||||
|
WebSocket.AllChat
|
||||||
WebSocket.Messages
|
WebSocket.Messages
|
||||||
WebSocket.MonadWebSocketSession
|
WebSocket.MonadWebSocketSession
|
||||||
WebSocket.Server
|
WebSocket.Server
|
||||||
|
@ -90,4 +92,3 @@ executable jitsi-rooms-exe
|
||||||
, warp
|
, warp
|
||||||
, websockets
|
, websockets
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
|
|
@ -58,14 +58,4 @@ executables:
|
||||||
dependencies:
|
dependencies:
|
||||||
- jitsi-rooms
|
- jitsi-rooms
|
||||||
|
|
||||||
tests:
|
|
||||||
jitsi-rooms-test:
|
|
||||||
main: Spec.hs
|
|
||||||
source-dirs: test
|
|
||||||
ghc-options:
|
|
||||||
- -threaded
|
|
||||||
- -rtsopts
|
|
||||||
- -with-rtsopts=-N
|
|
||||||
dependencies:
|
|
||||||
- jitsi-rooms
|
|
||||||
default-extensions: NoImplicitPrelude,OverloadedStrings,ImportQualifiedPost
|
default-extensions: NoImplicitPrelude,OverloadedStrings,ImportQualifiedPost
|
||||||
|
|
|
@ -7,12 +7,11 @@ where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Data.Aeson (encode)
|
import Data.Aeson (encode)
|
||||||
import qualified Network.WebSockets as WS
|
import Network.WebSockets qualified as WS
|
||||||
import State.ConnectedClientsState (MonadConnectedClientsRead (getConnctedClients))
|
import State.ConnectedClientsState (MonadConnectedClientsRead (getConnctedClients))
|
||||||
import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState))
|
import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState))
|
||||||
import Types.AppTypes (HasConnectedClientState (..))
|
|
||||||
import Types.ConnectionState (Client (..), ConnectedClients)
|
import Types.ConnectionState (Client (..), ConnectedClients)
|
||||||
import Types.RoomsState (HasRoomsState (..))
|
import Types.User (User, clientToUser)
|
||||||
import Types.UsersData (UsersData (..))
|
import Types.UsersData (UsersData (..))
|
||||||
|
|
||||||
class (Monad m, MonadConnectedClientsRead m) => MonadBroadcast m where
|
class (Monad m, MonadConnectedClientsRead m) => MonadBroadcast m where
|
||||||
|
@ -32,8 +31,8 @@ broadcastUserData = do
|
||||||
getUsersWithoutRoom ::
|
getUsersWithoutRoom ::
|
||||||
( MonadConnectedClientsRead m
|
( MonadConnectedClientsRead m
|
||||||
) =>
|
) =>
|
||||||
m [Text]
|
m [User]
|
||||||
getUsersWithoutRoom = map name . filter (not . joinedRoom) <$> getConnctedClients
|
getUsersWithoutRoom = map clientToUser . filter (not . joinedRoom) <$> getConnctedClients
|
||||||
|
|
||||||
broadCastToClientsGeneric ::
|
broadCastToClientsGeneric ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
|
||||||
|
module Types.User (User, clientToUser) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Data.Aeson (FromJSON, ToJSON)
|
||||||
|
import Data.UUID (UUID)
|
||||||
|
import Types.ConnectionState qualified as C
|
||||||
|
|
||||||
|
data User = User
|
||||||
|
{ uuid :: UUID,
|
||||||
|
name :: Text
|
||||||
|
}
|
||||||
|
deriving (Generic, Show, Eq)
|
||||||
|
|
||||||
|
clientToUser :: C.Client -> User
|
||||||
|
clientToUser C.Client {C.uuid = userUuid, C.name = userName, C.joinedRoom = _, C.conn = _} = User userUuid userName
|
||||||
|
|
||||||
|
instance ToJSON User
|
||||||
|
|
||||||
|
instance FromJSON User
|
|
@ -8,6 +8,7 @@ where
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Data.Aeson (ToJSON)
|
import Data.Aeson (ToJSON)
|
||||||
import Types.RoomData (RoomsData)
|
import Types.RoomData (RoomsData)
|
||||||
|
import Types.User (User)
|
||||||
|
|
||||||
data UsersData = UsersData
|
data UsersData = UsersData
|
||||||
{ roomsData :: RoomsData,
|
{ roomsData :: RoomsData,
|
||||||
|
@ -17,4 +18,4 @@ data UsersData = UsersData
|
||||||
|
|
||||||
instance ToJSON UsersData
|
instance ToJSON UsersData
|
||||||
|
|
||||||
type UsersWithoutRoom = [Text]
|
type UsersWithoutRoom = [User]
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
|
||||||
module Types.WebSocketMessages.WebSocketMessages
|
module Types.WebSocketMessages.WebSocketMessages
|
||||||
( WebSocketMessage (..),
|
( WebSocketMessage (..),
|
||||||
SetClientInfo (..),
|
SetClientInfo (..),
|
||||||
JoinRoom (..),
|
JoinRoom (..),
|
||||||
|
AllChatMessageIncoming (..),
|
||||||
|
AllChatMessageOutgoing (..),
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -12,14 +15,13 @@ import Data.Aeson
|
||||||
( FromJSON (parseJSON),
|
( FromJSON (parseJSON),
|
||||||
Options (sumEncoding),
|
Options (sumEncoding),
|
||||||
SumEncoding (..),
|
SumEncoding (..),
|
||||||
decode,
|
ToJSON,
|
||||||
defaultOptions,
|
defaultOptions,
|
||||||
genericParseJSON,
|
genericParseJSON,
|
||||||
withObject,
|
|
||||||
(.:),
|
|
||||||
)
|
)
|
||||||
|
import Types.User (User)
|
||||||
|
|
||||||
data WebSocketMessage = ClientInfoMessage SetClientInfo | JoinRoomMessage JoinRoom
|
data WebSocketMessage = ClientInfoMessage SetClientInfo | JoinRoomMessage JoinRoom | AllChatMessageIncomingMessage AllChatMessageIncoming
|
||||||
deriving (Generic)
|
deriving (Generic)
|
||||||
|
|
||||||
instance FromJSON WebSocketMessage where
|
instance FromJSON WebSocketMessage where
|
||||||
|
@ -38,3 +40,18 @@ data JoinRoom = JoinRoom
|
||||||
deriving (Generic, Show)
|
deriving (Generic, Show)
|
||||||
|
|
||||||
instance FromJSON JoinRoom
|
instance FromJSON JoinRoom
|
||||||
|
|
||||||
|
data AllChatMessageIncoming = AllChatMessageIncoming
|
||||||
|
{ content :: Text
|
||||||
|
}
|
||||||
|
deriving (Generic, Show)
|
||||||
|
|
||||||
|
instance FromJSON AllChatMessageIncoming
|
||||||
|
|
||||||
|
data AllChatMessageOutgoing = AllChatMessageOutgoing
|
||||||
|
{ content :: Text,
|
||||||
|
sender :: User
|
||||||
|
}
|
||||||
|
deriving (Generic, Show)
|
||||||
|
|
||||||
|
instance ToJSON AllChatMessageOutgoing
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
|
||||||
|
module WebSocket.AllChat (broadCastAllChatMessage) where
|
||||||
|
|
||||||
|
import BroadcastUserData (MonadBroadcast (..))
|
||||||
|
import ClassyPrelude
|
||||||
|
import Data.Aeson (encode)
|
||||||
|
import Types.User (clientToUser)
|
||||||
|
import Types.WebSocketMessages.WebSocketMessages (AllChatMessageIncoming (..), AllChatMessageOutgoing (AllChatMessageOutgoing))
|
||||||
|
import WebSocket.MonadWebSocketSession (MonadWebSocketSession (getClient))
|
||||||
|
|
||||||
|
broadCastAllChatMessage :: (MonadBroadcast m, MonadWebSocketSession m) => AllChatMessageIncoming -> m ()
|
||||||
|
broadCastAllChatMessage AllChatMessageIncoming {content = message} = do
|
||||||
|
getClient >>= \case
|
||||||
|
Nothing -> return ()
|
||||||
|
Just client -> do
|
||||||
|
let broadCastValue = AllChatMessageOutgoing message (clientToUser client)
|
||||||
|
broadCastToClients $ (decodeUtf8 . toStrict . encode) broadCastValue
|
|
@ -15,6 +15,7 @@ import Data.Aeson
|
||||||
import Data.UUID (UUID)
|
import Data.UUID (UUID)
|
||||||
import State.ConnectedClientsState
|
import State.ConnectedClientsState
|
||||||
( MonadConnectedClientsModify,
|
( MonadConnectedClientsModify,
|
||||||
|
MonadConnectedClientsRead (getConnctedClients),
|
||||||
removeWSClient,
|
removeWSClient,
|
||||||
)
|
)
|
||||||
import State.RoomDataState (MonadRoomDataStateRead)
|
import State.RoomDataState (MonadRoomDataStateRead)
|
||||||
|
@ -23,9 +24,10 @@ import Types.WebSocketMessages.WebSocketMessages (SetClientInfo (..))
|
||||||
import WebSocket.Messages
|
import WebSocket.Messages
|
||||||
import WebSocket.WSReaderTApp
|
import WebSocket.WSReaderTApp
|
||||||
|
|
||||||
class Monad m => MonadWebSocketSession m where
|
class MonadConnectedClientsRead m => MonadWebSocketSession m where
|
||||||
getTypedWSMessage :: FromJSON a => m a
|
getTypedWSMessage :: FromJSON a => m a
|
||||||
getSesssionId :: m UUID
|
getSesssionId :: m UUID
|
||||||
|
getClient :: m (Maybe Client)
|
||||||
|
|
||||||
instance MonadWebSocketSession (WSApp WSEnv) where
|
instance MonadWebSocketSession (WSApp WSEnv) where
|
||||||
getTypedWSMessage = do
|
getTypedWSMessage = do
|
||||||
|
@ -36,6 +38,9 @@ instance MonadWebSocketSession (WSApp WSEnv) where
|
||||||
sendMessage $ "Bad message: " <> pack err
|
sendMessage $ "Bad message: " <> pack err
|
||||||
getTypedWSMessage
|
getTypedWSMessage
|
||||||
getSesssionId = getClientId <$> ask
|
getSesssionId = getClientId <$> ask
|
||||||
|
getClient = do
|
||||||
|
id' <- getSesssionId
|
||||||
|
find ((== id') . uuid) <$> getConnctedClients
|
||||||
|
|
||||||
class (Monad m) => MonadWebSocketSessionInit m where
|
class (Monad m) => MonadWebSocketSessionInit m where
|
||||||
newClient :: SetClientInfo -> m Client
|
newClient :: SetClientInfo -> m Client
|
||||||
|
|
|
@ -13,6 +13,7 @@ import Types.WebSocketMessages.WebSocketMessages
|
||||||
( SetClientInfo (displayName),
|
( SetClientInfo (displayName),
|
||||||
WebSocketMessage (..),
|
WebSocketMessage (..),
|
||||||
)
|
)
|
||||||
|
import WebSocket.AllChat (broadCastAllChatMessage)
|
||||||
import WebSocket.MonadWebSocketSession
|
import WebSocket.MonadWebSocketSession
|
||||||
import WebSocket.WSReaderTApp
|
import WebSocket.WSReaderTApp
|
||||||
|
|
||||||
|
@ -35,7 +36,8 @@ wsApp = do
|
||||||
|
|
||||||
handleWSAction ::
|
handleWSAction ::
|
||||||
( MonadWebSocketSession m,
|
( MonadWebSocketSession m,
|
||||||
MonadConnectedClientsModify m
|
MonadConnectedClientsModify m,
|
||||||
|
MonadBroadcast m
|
||||||
) =>
|
) =>
|
||||||
m ()
|
m ()
|
||||||
handleWSAction = do
|
handleWSAction = do
|
||||||
|
@ -45,6 +47,8 @@ handleWSAction = do
|
||||||
joinRoom
|
joinRoom
|
||||||
ClientInfoMessage clientInfo -> do
|
ClientInfoMessage clientInfo -> do
|
||||||
updateClientName clientInfo
|
updateClientName clientInfo
|
||||||
|
AllChatMessageIncomingMessage incomingMessage -> do
|
||||||
|
broadCastAllChatMessage incomingMessage
|
||||||
|
|
||||||
joinRoom ::
|
joinRoom ::
|
||||||
( MonadConnectedClientsModify m,
|
( MonadConnectedClientsModify m,
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
main :: IO ()
|
|
||||||
main = putStrLn "Test suite not yet implemented"
|
|
|
@ -10,6 +10,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jitsi/react-sdk": "^1.3.0",
|
"@jitsi/react-sdk": "^1.3.0",
|
||||||
|
"jotai": "^2.0.3",
|
||||||
"just-curry-it": "^5.3.0",
|
"just-curry-it": "^5.3.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Provider } from "jotai";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import Meeting from "./components/meeting/Meeting";
|
import Meeting from "./components/meeting/Meeting";
|
||||||
|
@ -9,7 +10,6 @@ import { useRoomName } from "./hooks/useRoomName";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { userInfo, setUserInfo } = useLocalUser();
|
const { userInfo, setUserInfo } = useLocalUser();
|
||||||
const { roomName, updateRoomName, updateAndSubmitRoomName, submitRoomName } = useRoomName();
|
|
||||||
const { roomData, sendMessage } = useBackendData(userInfo);
|
const { roomData, sendMessage } = useBackendData(userInfo);
|
||||||
const { conferenceData, setConferenceData } = useConferenceData(
|
const { conferenceData, setConferenceData } = useConferenceData(
|
||||||
sendMessage,
|
sendMessage,
|
||||||
|
@ -23,20 +23,12 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Sidebar usersData={roomData}
|
<Sidebar usersData={roomData}
|
||||||
updateAndSubmitRoomName={(roomName: string) => {
|
sendMessage={sendMessage}
|
||||||
updateAndSubmitRoomName(roomName)
|
|
||||||
setMeetingStarted(true)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Meeting
|
<Meeting
|
||||||
conferenceData={conferenceData}
|
conferenceData={conferenceData}
|
||||||
setConferenceData={setConferenceData}
|
setConferenceData={setConferenceData}
|
||||||
userInfo={userInfo}
|
userInfo={userInfo}
|
||||||
roomName={roomName}
|
|
||||||
updateRoomName={updateRoomName}
|
|
||||||
submitRoomName={submitRoomName}
|
|
||||||
meetingStarted={meetingStarted}
|
|
||||||
setMeetingStarted={setMeetingStarted}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,7 +10,13 @@ export interface Participant {
|
||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
uuid: string,
|
||||||
|
name: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export interface UsersData {
|
export interface UsersData {
|
||||||
roomsData: RoomData[];
|
roomsData: RoomData[];
|
||||||
usersWithOutRoom: string[];
|
usersWithOutRoom: User[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
.chat-input{
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.chat-bubble{
|
||||||
|
background-color: #3d3d5c;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
|
||||||
|
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
|
||||||
|
white-space: -pre-wrap; /* Opera 4-6 */
|
||||||
|
white-space: -o-pre-wrap; /* Opera 7 */
|
||||||
|
white-space: pre-wrap; /* css-3 */
|
||||||
|
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||||
|
white-space: -webkit-pre-wrap; /* Newer versions of Chrome/Safari*/
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { FormEventHandler } from "react";
|
||||||
|
import useAllChat from "../../hooks/useAllChat";
|
||||||
|
import "./Chat.css"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sendMessage: Function
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function Chat({ sendMessage }: Props) {
|
||||||
|
|
||||||
|
const [chatInput, setChatInput] = useState("")
|
||||||
|
|
||||||
|
const { chatMesages } = useAllChat()
|
||||||
|
|
||||||
|
const onInput: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||||
|
setChatInput(event.target.value);
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit: FormEventHandler<HTMLFormElement> = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
sendMessage(JSON.stringify({ content: chatInput }));
|
||||||
|
setChatInput("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
{chatMesages.map(message => (
|
||||||
|
<div className="chat-bubble"> <span>{message.sender.name}: </span> {message.content} </div>
|
||||||
|
))
|
||||||
|
|
||||||
|
}
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<input
|
||||||
|
className="chat-input"
|
||||||
|
placeholder="Say something funny :D"
|
||||||
|
type="text"
|
||||||
|
value={chatInput}
|
||||||
|
onChange={onInput}
|
||||||
|
/>
|
||||||
|
<button type="submit">Send</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Chat
|
|
@ -1,5 +1,6 @@
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { ConferenceData } from "../../background/jitsi/eventListeners";
|
import { ConferenceData } from "../../background/jitsi/eventListeners";
|
||||||
|
import useMeetingStarted from "../../hooks/useMeetingStarted";
|
||||||
import { useRoomName } from "../../hooks/useRoomName";
|
import { useRoomName } from "../../hooks/useRoomName";
|
||||||
import JitsiEntrypoint from "../jitsi/JitsiEntrypoint";
|
import JitsiEntrypoint from "../jitsi/JitsiEntrypoint";
|
||||||
import { UserInfo } from "../jitsi/types";
|
import { UserInfo } from "../jitsi/types";
|
||||||
|
@ -9,17 +10,12 @@ interface Props {
|
||||||
conferenceData: ConferenceData | undefined;
|
conferenceData: ConferenceData | undefined;
|
||||||
setConferenceData: (newData: ConferenceData) => void;
|
setConferenceData: (newData: ConferenceData) => void;
|
||||||
userInfo: UserInfo;
|
userInfo: UserInfo;
|
||||||
//@ts-ignore
|
|
||||||
roomName, updateRoomName, submitRoomName
|
|
||||||
meetingStarted: Boolean, setMeetingStarted: Function
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Meeting({ conferenceData, setConferenceData, userInfo, roomName, updateRoomName, submitRoomName, meetingStarted, setMeetingStarted }: Props) {
|
function Meeting({ conferenceData, setConferenceData, userInfo }: Props) {
|
||||||
|
|
||||||
const startMeeting = useCallback(() => {
|
const [meetingStarted, setMeetingStarted] = useMeetingStarted()
|
||||||
submitRoomName();
|
const { roomName } = useRoomName()
|
||||||
setMeetingStarted(true);
|
|
||||||
}, [submitRoomName, setMeetingStarted]);
|
|
||||||
|
|
||||||
if (meetingStarted) {
|
if (meetingStarted) {
|
||||||
return (
|
return (
|
||||||
|
@ -35,8 +31,6 @@ function Meeting({ conferenceData, setConferenceData, userInfo, roomName, update
|
||||||
return (
|
return (
|
||||||
<MeetingNameInput
|
<MeetingNameInput
|
||||||
roomName={roomName}
|
roomName={roomName}
|
||||||
setName={updateRoomName}
|
|
||||||
submit={startMeeting}
|
|
||||||
currentUser={userInfo.displayName}
|
currentUser={userInfo.displayName}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,26 +1,35 @@
|
||||||
import { FormEventHandler } from "react";
|
import { FormEventHandler } from "react";
|
||||||
|
import useMeetingStarted from "../../hooks/useMeetingStarted";
|
||||||
|
import { useRoomName } from "../../hooks/useRoomName";
|
||||||
import "./MeetingNameInput.css";
|
import "./MeetingNameInput.css";
|
||||||
|
|
||||||
function MeetingNameInput(props: {
|
function MeetingNameInput(props: {
|
||||||
roomName: string;
|
roomName: string; currentUser: string;
|
||||||
setName: (name: string) => void;
|
|
||||||
submit: FormEventHandler;
|
|
||||||
currentUser: string;
|
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
const { roomName, updateRoomName, updateAndSubmitRoomName, submitRoomName } = useRoomName()
|
||||||
|
const [_, setMeetingStarted] = useMeetingStarted()
|
||||||
|
|
||||||
const onInput: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
const onInput: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||||
props.setName(event.target.value);
|
updateRoomName(event.target.value);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSubmit: FormEventHandler<HTMLFormElement> = (event) => {
|
||||||
|
submitRoomName()
|
||||||
|
setMeetingStarted(true)
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
console.log("[Rooms] MeetingName input comp");
|
console.log("[Rooms] MeetingName input comp");
|
||||||
return (
|
return (
|
||||||
<div className="meeting-name-input">
|
<div className="meeting-name-input">
|
||||||
<h1>Greetings {props.currentUser}</h1>
|
<h1>Greetings {props.currentUser}</h1>
|
||||||
<form onSubmit={props.submit}>
|
<form onSubmit={onSubmit}>
|
||||||
<input
|
<input
|
||||||
placeholder="Roomname"
|
placeholder="Roomname"
|
||||||
type="text"
|
type="text"
|
||||||
value={props.roomName}
|
value={roomName}
|
||||||
onChange={onInput}
|
onChange={onInput}
|
||||||
/>
|
/>
|
||||||
<button type="submit">Enter the adventure</button>
|
<button type="submit">Enter the adventure</button>
|
||||||
|
|
|
@ -25,7 +25,11 @@
|
||||||
max-width: 220px;
|
max-width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-hidden > .sidebar-footer {
|
.sidebar-hidden {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-hidden > .sidebar-footer > .sidebar-toggle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,20 @@ import SidebarHeader from "./SidebarHeader";
|
||||||
import useSidebarVisibility from "./useSidebarVisibility";
|
import useSidebarVisibility from "./useSidebarVisibility";
|
||||||
import "./Sidebar.css";
|
import "./Sidebar.css";
|
||||||
import { UsersData } from "../../background/types/roomData";
|
import { UsersData } from "../../background/types/roomData";
|
||||||
|
import useMeetingStarted from "../../hooks/useMeetingStarted";
|
||||||
|
import { useRoomName } from "../../hooks/useRoomName";
|
||||||
|
import Chat from "../chat/Chat";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
usersData: UsersData;
|
usersData: UsersData;
|
||||||
updateAndSubmitRoomName: Function
|
sendMessage: Function
|
||||||
}
|
}
|
||||||
|
|
||||||
function Sidebar(props: Props) {
|
function Sidebar(props: Props) {
|
||||||
const { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText } =
|
const { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText } =
|
||||||
useSidebarVisibility();
|
useSidebarVisibility();
|
||||||
|
const [_, setMeetingStarted] = useMeetingStarted()
|
||||||
|
const { updateAndSubmitRoomName: updateAndSubmitRoomName } = useRoomName();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`sidebar sidebar-${sidebarVisibility}`}>
|
<div className={`sidebar sidebar-${sidebarVisibility}`}>
|
||||||
|
@ -21,13 +26,14 @@ function Sidebar(props: Props) {
|
||||||
<>
|
<>
|
||||||
<h3>
|
<h3>
|
||||||
<a href="#" onClick={() => {
|
<a href="#" onClick={() => {
|
||||||
props.updateAndSubmitRoomName(roomData.roomName)
|
updateAndSubmitRoomName(roomData.roomName)
|
||||||
|
setMeetingStarted(true)
|
||||||
}}>
|
}}>
|
||||||
{roomData.roomName}
|
{roomData.roomName}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
{roomData.participants.map((participant) => (
|
{roomData.participants.map((participant) => (
|
||||||
<div> {participant.displayName} </div>
|
<div key={participant.jid}> {participant.displayName} </div>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -35,12 +41,13 @@ function Sidebar(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3> No room</h3>
|
<h3> No room</h3>
|
||||||
{props.usersData.usersWithOutRoom.map((username) => (
|
{props.usersData.usersWithOutRoom.map((user) => (
|
||||||
<div>{username}</div>
|
<div key={user.uuid}>{user.name}</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="sidebar-footer">
|
<div className="sidebar-footer">
|
||||||
<button onClick={toggleSidebarVisibility}>{sidebarToggleText}</button>
|
<Chat sendMessage={props.sendMessage} />
|
||||||
|
<button className="sidebar-toggle" onClick={toggleSidebarVisibility}>{sidebarToggleText}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { atom, useAtom } from "jotai";
|
||||||
|
import { atomWithReducer, useReducerAtom } from 'jotai/utils'
|
||||||
|
import { User } from "../background/types/roomData";
|
||||||
|
|
||||||
|
interface ChatMessage {
|
||||||
|
content: string
|
||||||
|
sender: User
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const allChatMessagesAtom = atomWithReducer([], (list: ChatMessage[], item: ChatMessage) => list.concat(item))
|
||||||
|
|
||||||
|
|
||||||
|
const useAllChat = () => {
|
||||||
|
const [chatMessages, addChatMessage] = useAtom(allChatMessagesAtom)
|
||||||
|
|
||||||
|
|
||||||
|
return { chatMesages: chatMessages, addChatMessage }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default useAllChat
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { UserInfo } from "../components/jitsi/types";
|
import { UserInfo } from "../components/jitsi/types";
|
||||||
|
import useAllChat from "./useAllChat";
|
||||||
import useRoomData from "./useRoomData";
|
import useRoomData from "./useRoomData";
|
||||||
import useWebSocketConnection from "./useWebSocketConnection";
|
import useWebSocketConnection from "./useWebSocketConnection";
|
||||||
|
|
||||||
|
@ -10,12 +11,15 @@ function useBackendData(userInfo: UserInfo) {
|
||||||
|
|
||||||
const { roomData, setRoomData } = useRoomData();
|
const { roomData, setRoomData } = useRoomData();
|
||||||
|
|
||||||
|
const { chatMesages, addChatMessage } = useAllChat()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onMessage((messageString) => {
|
onMessage((messageString) => {
|
||||||
console.log("[Rooms] message from ws", messageString);
|
console.log("[Rooms] message from ws", messageString);
|
||||||
const messageObject = JSON.parse(messageString);
|
const messageObject = JSON.parse(messageString);
|
||||||
|
|
||||||
!!messageObject.roomsData && setRoomData(messageObject);
|
!!messageObject.roomsData && setRoomData(messageObject);
|
||||||
|
!!messageObject.content && addChatMessage(messageObject);
|
||||||
return disconnect;
|
return disconnect;
|
||||||
});
|
});
|
||||||
}, [onMessage, setRoomData, disconnect]);
|
}, [onMessage, setRoomData, disconnect]);
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { atom, useAtom } from "jotai";
|
||||||
|
|
||||||
|
const meetingStarted = atom(false)
|
||||||
|
|
||||||
|
const useMeetingStarted = () => useAtom(meetingStarted);
|
||||||
|
|
||||||
|
export default useMeetingStarted
|
|
@ -1,7 +1,10 @@
|
||||||
|
import { atom, useAtom } from "jotai";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
|
const roomNameAtom = atom(getRoomNameFromUrl())
|
||||||
|
|
||||||
function useRoomName() {
|
function useRoomName() {
|
||||||
const [roomName, setRoomName] = useState(() => getRoomNameFromUrl());
|
const [roomName, setRoomName] = useAtom(roomNameAtom)
|
||||||
|
|
||||||
const updateRoomName = useCallback(
|
const updateRoomName = useCallback(
|
||||||
(newName: string) => {
|
(newName: string) => {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Provider } from 'jotai'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
@ -5,6 +6,8 @@ import './index.css'
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<Provider>
|
||||||
<App />
|
<App />
|
||||||
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
|
|
@ -543,6 +543,11 @@ is-core-module@^2.9.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
|
|
||||||
|
jotai@^2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.0.3.tgz#3b67cda9f6d5feb70a14db0b842a9873aacda8b5"
|
||||||
|
integrity sha512-MMjhSPAL3RoeZD9WbObufRT2quThEAEknHHridf2ma8Ml7ZVQmUiHk0ssdbR3F0h3kcwhYqSGJ59OjhPge7RRg==
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
|
|
Loading…
Reference in New Issue