add socket.io back, cleanup
Change-Id: I74e043268c23fb45371f1e397ca2931ca177afc3
diff --git a/client/package.json b/client/package.json
index 191dd7f..5c4cc03 100644
--- a/client/package.json
+++ b/client/package.json
@@ -17,7 +17,7 @@
"react-emoji-render": "^1.2.4",
"react-router-dom": "^5.2.0",
"react-sound": "^1.2.0",
- "socket.io-client": "^2.3.0"
+ "socket.io-client": "^4.0.1"
},
"devDependencies": {
"@babel/plugin-transform-runtime": "^7.13.15",
diff --git a/client/src/AuthManager.js b/client/src/AuthManager.js
index 4b7b6b4..46b4466 100644
--- a/client/src/AuthManager.js
+++ b/client/src/AuthManager.js
@@ -142,7 +142,11 @@
fetch(url, init) {
console.log(`fetch ${url}`)
if (!this.state.authenticated) {
- return new Promise((resolve, reject) => this.tasks.push({url, init, resolve, reject}))
+ if (!init || !init.method || init.method === 'GET') {
+ return new Promise((resolve, reject) => this.tasks.push({url, init, resolve, reject}))
+ } else {
+ return new Promise((resolve, reject) => reject("Not authenticated"))
+ }
}
return fetch(url, init)
.then(response => {
diff --git a/client/src/components/AccountPreferences.js b/client/src/components/AccountPreferences.js
index ee09094..e14101f 100644
--- a/client/src/components/AccountPreferences.js
+++ b/client/src/components/AccountPreferences.js
@@ -1,13 +1,15 @@
-import React from 'react'
+import React, { useState } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import { List, ListItem, ListItemIcon, ListItemSecondaryAction, ListItemText, ListSubheader, Switch, Typography, Grid, Paper, CardContent, Card, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Toolbar, IconButton, ListItemAvatar, Input, TextField } from '@material-ui/core'
-import { PhoneCallbackRounded, GroupRounded, DeleteRounded, AccountCircle, AddCircle } from '@material-ui/icons'
+import { PhoneCallbackRounded, GroupRounded, DeleteRounded, AddCircle } from '@material-ui/icons'
import Account from '../../../model/Account'
import JamiIdCard from './JamiIdCard'
import ConversationAvatar from './ConversationAvatar'
import ConversationsOverviewCard from './ConversationsOverviewCard'
+import authManager from '../AuthManager'
+
const useStyles = makeStyles(theme => ({
root: {
minWidth: 275,
@@ -35,6 +37,18 @@
const isJamiAccount = account.getType() === Account.TYPE_JAMI
const alias = isJamiAccount ? "Jami account" : "SIP account"
const moderators = account.getDefaultModerators()
+ const [defaultModeratorUri, setDefaultModeratorUri] = useState('')
+
+ const addModerator = () => {
+ if (defaultModeratorUri) {
+ authManager.fetch(`/api/accounts/${account.getId()}/defaultModerators/${defaultModeratorUri}`, {method: "PUT"})
+ setDefaultModeratorUri('')
+ }
+ }
+
+ const removeModerator = (uri) =>
+ authManager.fetch(`/api/accounts/${account.getId()}/defaultModerators/${uri}`, {method: "DELETE"})
+
return (
<React.Fragment>
<Typography variant="h2" component="h2" gutterBottom>{alias}</Typography>
@@ -98,22 +112,28 @@
</Toolbar>
<List>
<ListItem key="add">
- <TextField variant="outlined" className={classes.textField} label="Add new default moderator" placeholder="Enter new moderator name or URI" fullWidth />
+ <TextField variant="outlined"
+ className={classes.textField}
+ value={defaultModeratorUri}
+ onChange={e => setDefaultModeratorUri(e.target.value)}
+ label="Add new default moderator"
+ placeholder="Enter new moderator name or URI"
+ fullWidth />
<ListItemSecondaryAction>
- <IconButton><AddCircle /></IconButton>
+ <IconButton onClick={addModerator}><AddCircle /></IconButton>
</ListItemSecondaryAction>
</ListItem>
- {moderators.length === 0 ?
+ {!moderators || moderators.length === 0 ?
<ListItem key="placeholder">
<ListItemText primary="No default moderator" /></ListItem> :
moderators.map((moderator) => (
- <ListItem key={moderator.name}>
+ <ListItem key={moderator.uri}>
<ListItemAvatar>
- <ConversationAvatar name={moderator.name} />
+ <ConversationAvatar name={moderator.getDisplayName()} />
</ListItemAvatar>
- <ListItemText primary={moderator.name} />
+ <ListItemText primary={moderator.getDisplayName()} />
<ListItemSecondaryAction>
- <IconButton><DeleteRounded /></IconButton>
+ <IconButton onClick={e => removeModerator(moderator.uri)}><DeleteRounded /></IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
diff --git a/client/src/components/ConversationView.js b/client/src/components/ConversationView.js
index 719d3d0..6217905 100644
--- a/client/src/components/ConversationView.js
+++ b/client/src/components/ConversationView.js
@@ -8,7 +8,7 @@
const ConversationView = props => {
const [state, setState] = useState({
- loaded:false,
+ loaded: false,
error: false,
conversation: undefined
})
@@ -44,15 +44,26 @@
})
}
+ const loadMore = () => {
+ authManager.fetch(`/api/accounts/${props.accountId}/conversations/${props.conversationId}/messages`)
+ .then(res => res.json())
+ .then(messages => {
+ console.log(messages)
+ state.conversation.addLoadedMessages(messages)
+ setState(state)
+ })
+ }
+
+ console.log("ConversationView render " + (state.conversation ? state.conversation.getMessages().length : "no conversation"))
if (state.loaded === false) {
return <LoadingPage />
} else if (state.error === true) {
return <div>Error loding {props.conversationId}</div>
} else {
- return <React.Fragment>
- <MessageList conversation={state.conversation} messages={state.conversation.getMessages()} />
+ return <div className="messenger">
+ <MessageList conversation={state.conversation} loadMore={loadMore} messages={state.conversation.getMessages()} />
<SendMessageForm onSend={sendMessage} />
- </React.Fragment>
+ </div>
}
}
diff --git a/client/src/components/Message.js b/client/src/components/Message.js
index f0aaba8..9054cd7 100644
--- a/client/src/components/Message.js
+++ b/client/src/components/Message.js
@@ -2,10 +2,13 @@
import React from 'react'
function Message(props) {
+ console.log("Message render")
+ console.log(props.message)
+
return (
<div className="message">
- <div className="message-username">{props.username}</div>
- <Typography className="message-text">{props.text}</Typography>
+ <div className="message-username">{props.message.author}</div>
+ <Typography className="message-text">{props.message.body}</Typography>
</div>
)
}
diff --git a/client/src/components/MessageList.js b/client/src/components/MessageList.js
index 94947ca..b2fca5e 100644
--- a/client/src/components/MessageList.js
+++ b/client/src/components/MessageList.js
@@ -1,13 +1,21 @@
import Message from './Message'
-import React from 'react'
+import React, { useEffect } from 'react'
import { Box, Divider, Typography } from '@material-ui/core'
import ConversationAvatar from './ConversationAvatar'
+const reverseMap = (arr, f) => arr.map((_, idx, arr) => f(arr[arr.length - 1 - idx ]));
export default function MessageList(props) {
const displayName = props.conversation.getDisplayName()
+ const messages = props.conversation.getMessages()
+ console.log("MessageList render " + messages.length)
+
+ useEffect(() => {
+ props.loadMore()
+ }, [props.conversation.getId()])
+
return (
- <div className="message-list">
- <Box>
+ <React.Fragment>
+ <Box className="conversation-header">
<Box style={{ display: 'inline-block', margin: 16, verticalAlign: 'middle' }}>
<ConversationAvatar displayName={props.conversation.getDisplayNameNoFallback()} />
</Box>
@@ -15,11 +23,13 @@
<Typography variant="h6">{displayName}</Typography>
<Typography variant="subtitle1">{props.conversation.getId()}</Typography>
</Box>
+ <Divider orientation="horizontal" />
</Box>
- <Divider orientation="horizontal" />
- {props.messages.map((message, index) =>
- <Message key={index} username={message.senderId} text={message.body} />
- )}
- </div>
+ <div className="message-list">
+ <div className="message-list-inner">
+ {reverseMap(messages, (message) => <Message key={message.id} message={message} />)}
+ </div>
+ </div>
+ </React.Fragment>
)
}
\ No newline at end of file
diff --git a/client/src/components/SendMessageForm.js b/client/src/components/SendMessageForm.js
index fb06d49..82ee7af 100644
--- a/client/src/components/SendMessageForm.js
+++ b/client/src/components/SendMessageForm.js
@@ -7,7 +7,8 @@
const useStyles = makeStyles((theme) => ({
root: {
- margin: 16,
+ marginLeft: 16,
+ marginRight: 16,
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
diff --git a/client/src/index.scss b/client/src/index.scss
index 970825f..ab30741 100644
--- a/client/src/index.scss
+++ b/client/src/index.scss
@@ -30,7 +30,17 @@
"n m"
"r m"
"r m"
- "r s";
+ "r m";
+}
+
+.messenger {
+ grid-area: m;
+ display: grid;
+ grid-template-rows: 73px 1fr 72px;
+ grid-template-areas:
+ "h"
+ "m"
+ "i";
}
.MuiContainer-root {
@@ -53,12 +63,26 @@
grid-area: r;
}
+.conversation-header {
+ grid-area: h;
+}
+
.message-list {
grid-area: m;
+ position: relative;
+}
+
+.message-list-inner {
+ max-height: 100%;
+ position: absolute;
+ overflow-y: auto;
+ width: 100%;
+ bottom: 0;
+ padding: 24px;
}
.send-message-form {
- grid-area: s;
+ grid-area: i;
}
/* REST OF CSS */
@@ -88,83 +112,6 @@
text-overflow: ellipsis;
}
-/*
-.rooms-list {
- box-sizing: border-box;
- padding: 10px;
- background-color: var(--main-color);
- overflow: scroll;
- height: 100%;
-}
-
-.rooms-list ul {
- list-style-type: none;
- padding: 0;
- overflow: scoll;
-}
-
-.rooms-list li {
- margin: 10px 0;
-}
-
-.rooms-list h3 {
- margin: 5px 0;
- color: var(--secondary-color);
-}
-
-.rooms-list .room a {
- color: var(--secondary-text-color);
- font-weight: 600;
- text-decoration: none;
-
-}
-
-.rooms-list .room.active a {
- color: var(--secondary-color);
-}
-*/
-/*
-.new-room-form {
- padding: 0 5px;
- background: var(--secondary-color);
- color: var(--main-text-color);
-}
-
-.new-room-form form {
- height: 100%;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.new-room-form input {
- width: 135px;
- background: var(--secondary-color);
-}
-
-.new-room-form button {
- background: var(--secondary-color);
- color: var(--main-text-color);
- border: 0;
-}
-
-.new-room-form input::placeholder {
- color: var(--main-text-color);
- font-weight: 200;
-}
-
-.new-room-form input:focus {
- outline-width: 0;
-}
-
-.new-room-form input {
- border: 0;
-}
-
-.new-room-form button {
- border: 0;
-}*/
-
.list-placeholder {
margin: 32px auto;
width: 256px;
@@ -197,50 +144,8 @@
.send-message-card {
border-radius: 8px;
margin: 16px;
- /*padding: 8px;*/
-}
-/*
-.message-list {
- box-sizing: border-box;
- padding-left: 6px;
- width: 100%;
- height: 100%;
- overflow: scroll;
- background: var(--third-color);
}
-.message-list .join-room {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100%;
- font-size: 34px;
- font-weight: 300;
-}*/
-/*
-.send-message-form {
- background: var(--send-message-form);
- display: flex;
-}
-
-.send-message-form input {
- width: 100%;
- padding: 15px 10px;
- margin: 0;
- border-style: none;
- background: var(--send-message-form);
- font-weight: 200;
-
-}
-
-.send-message-form input:focus {
- outline-width: 0;
-}
-
-.send-message-form input::placeholder {
- color: var(--main-text-color);
-}
-*/
.help-text {
position: absolute;
top: 10px;
diff --git a/client/src/pages/messenger.jsx b/client/src/pages/messenger.jsx
index 3fef47b..5b40718 100644
--- a/client/src/pages/messenger.jsx
+++ b/client/src/pages/messenger.jsx
@@ -1,14 +1,10 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import Header from '../components/Header'
-import ContactList from '../components/ContactList'
-import MessageList from '../components/MessageList'
-import SendMessageForm from '../components/SendMessageForm'
import NewContactForm from '../components/NewContactForm'
//import Sound from 'react-sound';
import io from "socket.io-client";
import ConversationList from '../components/ConversationList';
-import CircularProgress from '@material-ui/core/CircularProgress';
//const socket = io.connect('http://localhost:3000');
import authManager from '../AuthManager'
import Conversation from '../../../model/Conversation'
@@ -16,46 +12,26 @@
import ConversationView from '../components/ConversationView';
import AddContactPage from './addContactPage.jsx';
import LoadingPage from '../components/loading';
+import { useParams } from 'react-router';
-class JamiMessenger extends React.Component {
+const JamiMessenger = (props) => {
+ const [conversations, setConversations] = useState(undefined)
+ const [searchResult, setSearchResults] = useState(undefined)
- constructor(props) {
- super(props)
+ const params = useParams()
+ const accountId = props.accountId || params.accountId
+ const conversationId = props.conversationId || params.conversationId
+ const contactId = props.contactId || params.contactId
- this.state = {
- conversations: undefined,
- messages: [],
- sound: false
- }
-
+ //this.socket = socketIOClient(ENDPOINT);
/*socket.on('connect', () => {
console.log("Success !")
})*/
-
-
- //this.socket = socketIOClient(ENDPOINT);
- //this.socket.on("FromAPI", data => {
+ //this.socket.on("FromAPI", data => {
// this.setState({
// messages: [...this.state.messages, data]
// })
//});
- this.sendMessage = this.sendMessage.bind(this)
- this.handleSearch = this.handleSearch.bind(this)
- this.controller = new AbortController()
- }
-
- componentDidMount() {
- const accountId = this.props.accountId || this.props.match.params.accountId
- const conversationId = this.props.conversationId || this.props.match.params.conversationId
-
- if (this.req === undefined) {
- this.req = authManager.fetch(`/api/accounts/${accountId}/conversations`, {signal: this.controller.signal})
- .then(res => res.json())
- .then(result => {
- console.log(result)
- this.setState({conversations: Object.values(result).map(c => Conversation.from(accountId, c))})
- })
- }
/*socket.on('receivedMessage', (data) => {
const message = {
senderId: '65f6674b26e5af6ed0b4e92a13b80ff4bbfdf1e8',
@@ -66,17 +42,29 @@
sound: true
})
});*/
- }
+ useEffect(() => {
+ console.log("io.connect")
+ const socket = io()
+ socket.on('receivedMessage', (data) => {
+ console.log("receivedMessage")
+ console.log(data)
+ conversation.addMessage(data)
+ })
+ return () => socket.disconnect()
+ })
- componentWillUnmount(){
- this.controller.abort()
- this.req = undefined
- }
+ useEffect(() => {
+ const controller = new AbortController()
+ authManager.fetch(`/api/accounts/${accountId}/conversations`, {signal: controller.signal})
+ .then(res => res.json())
+ .then(result => {
+ console.log(result)
+ setConversations(Object.values(result).map(c => Conversation.from(accountId, c)))
+ })
+ return () => controller.abort()
+ }, [accountId])
- handleSearch(query) {
- const accountId = this.props.accountId || this.props.match.params.accountId
- const conversationId = this.props.conversationId || this.props.match.params.conversationId
-
+ const handleSearch = (query) => {
authManager.fetch(`/api/accounts/${accountId}/ns/name/${query}`)
.then(response => {
if (response.status === 200) {
@@ -88,54 +76,26 @@
console.log(response)
const contact = new Contact(response.address)
contact.setRegisteredName(response.name)
- this.setState({searchResult: contact ? Conversation.fromSingleContact(accountId, contact) : undefined})
+ setSearchResults(contact ? Conversation.fromSingleContact(accountId, contact) : undefined)
}).catch(e => {
- this.setState({searchResult: e})
+ setSearchResults(undefined)
})
}
- sendMessage(text) {
- var data = {
- senderId: 'Me',
- destinationId: '65f6674b26e5af6ed0b4e92a13b80ff4bbfdf1e8',
- text: text
- }
- //socket.emit("SendMessage", data);
- console.log(data.text);
- this.setState({
- messages: [...this.state.messages, data],
- sound: false
- })
- }
- render() {
- const accountId = this.props.accountId || this.props.match.params.accountId
- const conversationId = this.props.conversationId || this.props.match.params.conversationId
- const contactId = this.props.contactId || this.props.match.params.contactId
+ console.log("JamiMessenger render " + conversationId)
+ console.log(props)
- console.log("JamiMessenger render " + conversationId)
- console.log(this.props)
- console.log(this.state)
-
- return (
- <div className="app" >
- <Header />
- {this.state.conversations ?
- <ConversationList search={this.state.searchResult} conversations={this.state.conversations} accountId={accountId} /> :
- <div className="rooms-list"><LoadingPage /></div>}
- <NewContactForm onChange={query => this.handleSearch(query)} />
- {conversationId && <ConversationView accountId={accountId} conversationId={conversationId} />}
- {contactId && <AddContactPage accountId={accountId} contactId={contactId} />}
- {this.state.sound && <Sound
- url="stairs.mp3" /*https://notificationsounds.com/message-tones/stairs-567*/
- playStatus={Sound.status.PLAYING}
- playFromPosition={0 /* in milliseconds */}
- onLoading={this.handleSongLoading}
- onPlaying={this.handleSongPlaying}
- onFinishedPlaying={this.handleSongFinishedPlaying}
- />}
- </div>
- )
- }
+ return (
+ <div className="app" >
+ <Header />
+ {conversations ?
+ <ConversationList search={searchResult} conversations={conversations} accountId={accountId} /> :
+ <div className="rooms-list"><LoadingPage /></div>}
+ <NewContactForm onChange={handleSearch} />
+ {conversationId && <ConversationView accountId={accountId} conversationId={conversationId} />}
+ {contactId && <AddContactPage accountId={accountId} contactId={contactId} />}
+ </div>
+ )
}
export default JamiMessenger
\ No newline at end of file
diff --git a/client/src/pages/serverConfiguration.jsx b/client/src/pages/serverConfiguration.jsx
new file mode 100644
index 0000000..ba99fe6
--- /dev/null
+++ b/client/src/pages/serverConfiguration.jsx
@@ -0,0 +1,40 @@
+import React from 'react';
+import Header from '../components/Header'
+import AccountPreferences from '../components/AccountPreferences'
+import Container from '@material-ui/core/Container';
+import CircularProgress from '@material-ui/core/CircularProgress';
+import authManager from '../AuthManager'
+import Account from '../../../model/Account'
+
+const ServerOverview = (props) => {
+
+ this.accountId = props.accountId || props.match.params.accountId
+ this.state = { loaded: false, account: props.account }
+ this.req = undefined
+
+ componentDidMount() {
+ this.controller = new AbortController()
+ if (this.req === undefined) {
+ this.req = authManager.fetch(`/api/serverConfig`, {signal: this.controller.signal})
+ .then(res => res.json())
+ .then(result => {
+ console.log(result)
+ this.setState({loaded: true, account: Account.from(result)})
+ })
+ }
+ }
+
+ componentWillUnmount() {
+ this.controller.abort()
+ this.req = undefined
+ }
+
+ return (
+ <Container maxWidth="sm" className="app" >
+ <Header />
+ {this.state.loaded ? <AccountPreferences account={this.state.account} /> : <CircularProgress />}
+ </Container>
+ )
+}
+
+export default ServerOverview;
\ No newline at end of file
diff --git a/client/webpack.config.js b/client/webpack.config.js
index 0b67ce9..0e96882 100644
--- a/client/webpack.config.js
+++ b/client/webpack.config.js
@@ -1,16 +1,17 @@
'use strict'
-import dotenv from 'dotenv'
-dotenv.config({ path: resolve(import.meta.url, '..', '.env') })
-
-import { resolve } from 'path'
-import HtmlWebpackPlugin from 'html-webpack-plugin'
-import CopyWebpackPlugin from 'copy-webpack-plugin'
-
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
+
+import dotenv from 'dotenv'
+console.log(resolve(__dirname, '..', '.env'))
+dotenv.config({ path: resolve(__dirname, '..', '.env') })
+
+import { resolve } from 'path'
+import HtmlWebpackPlugin from 'html-webpack-plugin'
+import CopyWebpackPlugin from 'copy-webpack-plugin'
const mode = process.env.NODE_ENV || 'development'
let entry = [resolve(__dirname, 'src', 'index.js')]