diff options
author | 2019-04-08 15:42:24 +0300 | |
---|---|---|
committer | 2023-01-13 10:37:54 +0300 | |
commit | fccc23c05712b0b54e77daefd4b74855e52f08b6 (patch) | |
tree | e42ce2dc6595efb036b809f4a730160bbf3ec8d5 | |
parent | bbfb81c2bc8d6afba550299e7138eeb2c58956d3 (diff) |
App, Chat, Thread using hooks
-rw-r--r-- | vnext/src/App.js | 282 | ||||
-rw-r--r-- | vnext/src/components/Chat.js | 98 | ||||
-rw-r--r-- | vnext/src/components/Thread.js | 12 |
3 files changed, 180 insertions, 212 deletions
diff --git a/vnext/src/App.js b/vnext/src/App.js index 7978abe5..6fcf3d9c 100644 --- a/vnext/src/App.js +++ b/vnext/src/App.js @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom'; -import * as qs from 'qs'; +import qs from 'qs'; import Icon from './components/Icon'; import { Discover, Discussions, Blog, Tag, Home } from './components/Feeds'; @@ -11,179 +11,149 @@ import Chat from './components/Chat'; import Post from './components/Post'; import Thread from './components/Thread'; import LoginButton from './components/LoginButton'; -import Avatar from './components/Avatar'; import { UserLink } from './components/UserInfo'; import Header from './components/Header'; import SearchBox from './components/SearchBox'; -import NavigationIcon from './components/NavigationIcon'; import cookies from 'react-cookies'; import { me } from './api'; -const app = document.getElementById('app'); - -export default class App extends React.Component { - constructor(props) { - super(props); - let params = qs.parse(window.location.search.substring(1)); - if (params.hash) { - cookies.save('hash', params.hash, { path: '/' }); - window.history.replaceState({}, document.title, `${window.location.protocol}//${window.location.host}${window.location.pathname}`); - } - this.state = { - visitor: { - uid: 0, - hash: cookies.load('hash') - }, - appMarginLeft: 'inherit' - }; - this.pm = React.createRef(); - this.thread = React.createRef(); - this.sidebar = React.createRef(); +export default function App(props) { + let params = qs.parse(window.location.search.substring(1)); + if (params.hash) { + cookies.save('hash', params.hash, { path: '/' }); + window.history.replaceState({}, document.title, `${window.location.protocol}//${window.location.host}${window.location.pathname}`); } + const [visitor, setVisitor] = useState({ + uid: 0, + hash: cookies.load('hash') + }); + + let updateStatus = () => { + // refresh server visitor state (unread counters) + me().then(visitor => { + setVisitor(visitor); + }); + }; - initES = () => { - if (!('EventSource' in window)) { - return; + const [es, setEs] = useState(); + useEffect(() => { + const { hash } = visitor; + if (hash) { + me().then(visitor => auth(visitor)); } - const params = { hash: this.state.visitor.hash }; - let url = new URL(`https://api.juick.com/events?${qs.stringify(params)}`); - this.es = new EventSource(url); - this.es.onopen = () => { + const eventParams = { hash: visitor.hash }; + let url = new URL(`https://api.juick.com/events?${qs.stringify(eventParams)}`); + let es = new EventSource(url); + es.onopen = () => { console.log('online'); + es.addEventListener('read', updateStatus); }; - this.es.addEventListener('msg', msg => { - try { - var jsonMsg = JSON.parse(msg.data); - console.log('data: ' + msg.data); - // refresh server visitor state (unread counters) - me().then(visitor => { - this.setState({ - visitor: visitor - }); - }); - if (jsonMsg.service) { - return; - } - if (!jsonMsg.mid) { - this.pm.current.onMessage(jsonMsg); - } - } catch (err) { - console.log(err); - } - }); - } - - componentDidMount() { - const { hash } = this.state.visitor; - this.initES(); - if (hash) { - me().then(visitor => this.auth(visitor)); + es.onerror = () => { + es.removeEventListener('read', updateStatus); } - } - search = (history, pathname, searchString) => { + setEs(es); + }, []); + + + let search = (history, pathname, searchString) => { let location = {}; location.pathname = pathname; location.search = `?search=${searchString}`; history.push(location); } - render() { - const user = this.state.visitor; - return ( - <Router> - <> - <Header> - <div id="header_wrapper"> - { - user.uid > 0 ? - <UserLink user={user} /> - : <div id="logo"> - <Link to="/">Juick</Link> - </div> - } - <div id="search" className="desktop"> - <SearchBox pathname="/discover" onSearch={this.search} {...this.props} /> - </div> - <nav id="global"> - {user.uid > 0 ? - <Link to={{ pathname: '/' }}> - <Icon name="ei-bell" size="s" /><span className="desktop">Discuss</span> - { - user.unreadCount && - <span className="badge">{user.unreadCount}</span> - } - </Link> - : - <Link to='/?media=1' rel="nofollow"> - <Icon name="ei-camera" size="s" /> - <span className="desktop">Photos</span> - </Link> - } - <Link to={{ pathname: '/discover' }} rel="nofollow"> - <Icon name="ei-search" size="s" /> - <span className="desktop">Discover</span> - </Link> - - {user.uid > 0 ? - <Link to={{ pathname: '/post' }}> - <Icon name="ei-pencil" size="s" /> - <span className="desktop">Post</span> - </Link> - : - <LoginButton title="Login" onAuth={this.auth} /> - } - </nav> + let auth = (visitor) => { + setVisitor(visitor); + } + return ( + <Router> + <> + <Header> + <div id="header_wrapper"> + { + visitor.uid > 0 ? + <UserLink user={visitor} /> + : <div id="logo"> + <Link to="/">Juick</Link> + </div> + } + <div id="search" className="desktop"> + <SearchBox pathname="/discover" onSearch={search} {...props} /> </div> - </Header> - <section id="content"> - <Switch> - <Route exact path="/" render={(props) => <Discussions visitor={user} {...props} />} /> - <Route exact path="/home" render={(props) => <Home visitor={user} {...props} />} /> - <Route exact path="/discover" render={(props) => - <Discover visitor={user} {...props} /> - } /> - <Route exact path="/settings" render={(props) => - <Settings visitor={user} {...props} onChange={this.auth} /> - } /> - <Route exact path="/post" render={(props) => <Post visitor={user} {...props} />} /> - <Route exact path="/pm" render={(props) => <Contacts visitor={user} {...props} />} /> - <Route exact path="/pm/:user" render={(props) => <Chat connection={this.es} visitor={user} {...props} />} /> - <Route exact path="/:user/friends" render={(props) => <Friends user={props.match.params.user} {...props} />} /> - <Route exact path="/:user/readers" render={(props) => <Readers user={props.match.params.user} {...props} />} /> - <Route exact path="/:user" render={(props) => <Blog key={props.match.params.user} visitor={user} {...props} />} /> - <Route exact path="/tag/:tag" render={(props) => <Tag visitor={user} {...props} />} /> - <Route exact path="/:user/:mid" render={(props) => <Thread connection={this.es} visitor={user} {...props} />} /> - </Switch> - </section> - { - user.uid > 0 && - <aside id="sidebar" ref={this.sidebar}> - <Link to="/?show=my" onClick={this.toggleSidebar}> - <Icon name="ei-clock" size="s" /> - <span className="desktop">My feed</span> - </Link> - <Link to="/pm" onClick={this.toggleSidebar}> - <Icon name="ei-envelope" size="s" /> - <span className="desktop">Messages</span> - </Link> - <Link to="/?show=discuss" onClick={this.toggleSidebar}> - <Icon name="ei-bell" size="s" /> - <span className="desktop">Discussions</span> - </Link> - <Link to="/settings" rel="nofollow" onClick={this.toggleSidebar}> - <Icon name="ei-gear" size="s" /> - <span className="desktop">Settings</span> + <nav id="global"> + {visitor.uid > 0 ? + <Link to={{ pathname: '/' }}> + <Icon name="ei-bell" size="s" /><span className="desktop">Discuss</span> + { + visitor.unreadCount && + <span className="badge">{visitor.unreadCount}</span> + } + </Link> + : + <Link to='/?media=1' rel="nofollow"> + <Icon name="ei-camera" size="s" /> + <span className="desktop">Photos</span> + </Link> + } + <Link to={{ pathname: '/discover' }} rel="nofollow"> + <Icon name="ei-search" size="s" /> + <span className="desktop">Discover</span> </Link> - </aside> - } - </> - </Router> - ); - } - auth = (visitor) => { - this.setState({ - visitor: visitor - }); - } + + {visitor.uid > 0 ? + <Link to={{ pathname: '/post' }}> + <Icon name="ei-pencil" size="s" /> + <span className="desktop">Post</span> + </Link> + : + <LoginButton title="Login" onAuth={auth} /> + } + </nav> + </div> + </Header> + <section id="content"> + <Switch> + <Route exact path="/" render={(props) => <Discussions visitor={visitor} {...props} />} /> + <Route exact path="/home" render={(props) => <Home visitor={visitor} {...props} />} /> + <Route exact path="/discover" render={(props) => + <Discover visitor={visitor} {...props} /> + } /> + <Route exact path="/settings" render={(props) => + <Settings visitor={visitor} {...props} onChange={auth} /> + } /> + <Route exact path="/post" render={(props) => <Post visitor={visitor} {...props} />} /> + <Route exact path="/pm" render={(props) => <Contacts visitor={visitor} {...props} />} /> + <Route exact path="/pm/:user" render={(props) => <Chat connection={es} visitor={visitor} {...props} />} /> + <Route exact path="/:user/friends" render={(props) => <Friends user={props.match.params.user} {...props} />} /> + <Route exact path="/:user/readers" render={(props) => <Readers user={props.match.params.user} {...props} />} /> + <Route exact path="/:user" render={(props) => <Blog key={props.match.params.user} visitor={visitor} {...props} />} /> + <Route exact path="/tag/:tag" render={(props) => <Tag visitor={visitor} {...props} />} /> + <Route exact path="/:user/:mid" render={(props) => <Thread connection={es} visitor={visitor} {...props} />} /> + </Switch> + </section> + { + visitor.uid > 0 && + <aside id="sidebar"> + <Link to="/?show=my"> + <Icon name="ei-clock" size="s" /> + <span className="desktop">My feed</span> + </Link> + <Link to="/pm"> + <Icon name="ei-envelope" size="s" /> + <span className="desktop">Messages</span> + </Link> + <Link to="/?show=discuss"> + <Icon name="ei-bell" size="s" /> + <span className="desktop">Discussions</span> + </Link> + <Link to="/settings" rel="nofollow"> + <Icon name="ei-gear" size="s" /> + <span className="desktop">Settings</span> + </Link> + </aside> + } + </> + </Router> + ); } diff --git a/vnext/src/components/Chat.js b/vnext/src/components/Chat.js index 57415d29..e2b8d2f1 100644 --- a/vnext/src/components/Chat.js +++ b/vnext/src/components/Chat.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import ReactRouterPropTypes from 'react-router-prop-types'; import { UserType } from './Types'; import moment from 'moment'; @@ -11,74 +11,68 @@ import { getChat, pm } from '../api'; import './Chat.css'; -export default class Chat extends React.Component { - constructor(props) { - super(props); - - this.state = { - chats: [] +export default function Chat(props) { + const [chats, setChats] = useState([]); + useEffect(() => { + console.log(props.connection); + if (props.connection) { + props.connection.addEventListener('msg', onMessage); + } + loadChat(props.match.params.user); + return () => { + if (props.connection) { + props.connection.removeEventListener('msg', onMessage); + } }; - } - componentDidMount() { - this.loadChat(this.props.match.params.user); - } + }, [props.connection]); - loadChat = (uname) => { - const { hash } = this.props.visitor; - this.setState({ - chats: [] - }); + let loadChat = (uname) => { + const { hash } = props.visitor; + setChats([]); if (hash && uname) { getChat(uname) .then(response => { - this.setState({ - chats: response.data - }); + setChats(response.data); }); } } - onMessage = (msg) => { - if (msg.user.uname === this.props.match.params.user) { - this.setState({ - chats: [msg, ...this.state.chats] - }); + let onMessage = (json) => { + const msg = JSON.parse(json.data); + if (msg.user.uname === props.match.params.user) { + setChats([msg, ...chats]); } } - onSend = (template) => { + let onSend = (template) => { pm(template.to.uname, template.body) .then(res => { - this.loadChat(this.props.match.params.user); + loadChat(props.match.params.user); }).catch(console.log); } - - render() { - const { chats } = this.state; - const uname = this.props.match.params.user; - return ( - <div className="msg-cont"> - <UserInfo user={uname} /> - {uname ? ( - <div className="chatroom"> - <ul className="Chat_messages"> - { - chats.map((chat) => - <PM key={moment.utc(chat.timestamp).valueOf()} chat={chat} {...this.props} /> - ) - } - </ul> - <MessageInput data={{ mid: 0, timestamp: '0', to: { uname: uname } }} onSend={this.onSend}> - Reply... + const uname = props.match.params.user; + return ( + <div className="msg-cont"> + <UserInfo user={uname} /> + {uname ? ( + <div className="chatroom"> + <ul className="Chat_messages"> + { + chats.map((chat) => + <PM key={moment.utc(chat.timestamp).valueOf()} chat={chat} {...props} /> + ) + } + </ul> + <MessageInput data={{ mid: 0, timestamp: '0', to: { uname: uname } }} onSend={onSend}> + Reply... </MessageInput> - </div> - ) : ( - <div className="chatroom no-selection"><p>No chat selected</p></div> - ) - } - </div> - ); - } + </div> + ) : ( + <div className="chatroom no-selection"><p>No chat selected</p></div> + ) + } + </div> + ); } Chat.propTypes = { diff --git a/vnext/src/components/Thread.js b/vnext/src/components/Thread.js index d61fd87f..c1dc915b 100644 --- a/vnext/src/components/Thread.js +++ b/vnext/src/components/Thread.js @@ -22,13 +22,17 @@ export default function Thread(props) { const [loading, setLoading] = useState(false); const [active, setActive] = useState(0); useEffect(() => { - props.connection.addEventListener('msg', onReply); + if (props.connection) { + props.connection.addEventListener('msg', onReply); + } setActive(0); loadReplies(); return () => { - props.connection.removeEventListener('msg', onReply); + if (props.connection) { + props.connection.removeEventListener('msg', onReply); + } } - }, []); + }, [props.connection]); let loadReplies = () => { document.body.scrollTop = 0; document.documentElement.scrollTop = 0; @@ -54,7 +58,7 @@ export default function Thread(props) { }); } let onReply = (json) => { - const msg = JSON.parse(json); + const msg = JSON.parse(json.data); if (msg.mid == message.mid) { setReplies([...this.state.replies, msg]); } |