Skip to content

Commit

Permalink
Add ChatRoom
Browse files Browse the repository at this point in the history
  • Loading branch information
heyrict committed Jan 19, 2018
1 parent 5e87198 commit 495388f
Show file tree
Hide file tree
Showing 29 changed files with 987 additions and 59 deletions.
8 changes: 2 additions & 6 deletions react-boilerplate/app/components/UserAwardPopover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,9 @@ class UserAwardPopover extends React.PureComponent {
);
return (
<OverlayTrigger placement="top" trigger="focus" overlay={popoverAward}>
<a
href="#!"
role="button"
style={{ color: 'black', ...this.props.style }}
>
<button style={{ color: 'black', padding: '0', ...this.props.style }}>
[{ua.award.name}]
</a>
</button>
</OverlayTrigger>
);
}
Expand Down
97 changes: 80 additions & 17 deletions react-boilerplate/app/containers/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
*/

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Switch, Route, withRouter } from 'react-router-dom';
import { Flex, Box } from 'rebass';
import { createStructuredSelector } from 'reselect';
import { connect } from 'react-redux';

import WebSocketInterface from 'containers/WebSocketInterface';
import Notifier from 'containers/Notifier';
Expand All @@ -22,26 +27,84 @@ import PuzzlePage from 'containers/PuzzlePage/Loadable';
import PuzzleAddPage from 'containers/PuzzleAddPage/Loadable';
import PuzzleShowPage from 'containers/PuzzleShowPage';
import NotFoundPage from 'containers/NotFoundPage/Loadable';
import Chat from 'containers/Chat';
import makeSelectChat from 'containers/Chat/selectors';
import common from 'common';

import 'bootstrap/dist/css/bootstrap.min.css';
import 'alertifyjs/build/css/alertify.min.css';
import 'alertifyjs/build/css/themes/semantic.min.css';

export default function App() {
common.StartCountdown();
return (
<div>
<Notifier />
<WebSocketInterface />
<TopNavbar />
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/puzzle" component={PuzzlePage} />
<Route exact path="/puzzle/add" component={PuzzleAddPage} />
<Route path="/puzzle/show/:id" component={PuzzleShowPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
);
const EasingBox = styled(Box)`
height: ${(props) => props.height - 50}px;
overflow-y: auto;
@media (max-width: 720px) {
display: ${(props) =>
props.w === 0 || props.w[0] === 0 ? 'none' : 'block'};
}
`;

class App extends React.Component {
constructor(props) {
super(props);

this.resize = this.resize.bind(this);
}
componentWillMount() {
this.resize();
}
componentDidMount() {
this.countdownHdl = common.StartCountdown();
window.addEventListener('resize', this.resize);
}
componentWillUnmount() {
window.clearInterval(this.countdownHdl);
window.removeEventListener('resize', this.resize);
}
resize() {
const height = window.innerHeight || document.documentElement.clientHeight;
this.setState({ height });
}
render() {
return (
<div>
<Notifier />
<WebSocketInterface />
<TopNavbar />
<Flex>
<EasingBox
w={this.props.chat.open ? [1, 0.382, 0.3] : 0}
height={this.state.height}
hidden={!this.props.chat.open}
>
<Chat height={this.state.height - 50} />
</EasingBox>
<EasingBox
w={this.props.chat.open ? [0, 0.618, 0.7] : 1}
height={this.state.height}
>
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/puzzle" component={PuzzlePage} />
<Route exact path="/puzzle/add" component={PuzzleAddPage} />
<Route path="/puzzle/show/:id" component={PuzzleShowPage} />
<Route component={NotFoundPage} />
</Switch>
</EasingBox>
</Flex>
</div>
);
}
}

App.propTypes = {
chat: PropTypes.shape({
open: PropTypes.bool.isRequired,
}),
};

const mapStateToProps = createStructuredSelector({
chat: makeSelectChat(),
});

export default withRouter(connect(mapStateToProps)(App));
14 changes: 14 additions & 0 deletions react-boilerplate/app/containers/App/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { fromJS } from 'immutable';

const initialState = fromJS({
chatWidth: 0,
});

function chatReducer(state = initialState, action) {
switch (action.type) {
default:
return state;
}
}

export default chatReducer;
12 changes: 5 additions & 7 deletions react-boilerplate/app/containers/App/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import { createSelector } from 'reselect';

const selectRoute = (state) => state.get('route');

const makeSelectLocation = () => createSelector(
selectRoute,
(routeState) => routeState.get('location').toJS()
);
const makeSelectLocation = () =>
createSelector(selectRoute, (routeState) =>
routeState.get('location').toJS()
);

export {
makeSelectLocation,
};
export { makeSelectLocation };
74 changes: 74 additions & 0 deletions react-boilerplate/app/containers/Chat/Channels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Flex } from 'rebass';
import { ButtonOutline, Input } from 'style-store';
import { PublicChannels } from './constants';
import messages from './messages';

const StyledButton = ButtonOutline.extend`
border-radius: 5px;
padding: 10px;
`;

class Channels extends React.PureComponent {
constructor(props) {
super(props);

this.state = { content: '' };
this.handleChange = (e) => this.setState({ content: e.target.value });
this.handleKeyDown = (e) => {
if (e.key === 'Enter') this.props.tune(this.state.content);
};
}
render() {
return (
<Flex wrap>
<StyledButton
w={1}
py={20}
my={5}
onClick={() => this.props.tune(null)}
>
<FormattedMessage {...messages.defaultChannel} />
</StyledButton>
{PublicChannels.map((c) => (
<StyledButton
w={1}
py={20}
my={5}
onClick={() => this.props.tune(c)}
key={c}
>
{c}
</StyledButton>
))}
<Flex mx={1} w={1}>
<FormattedMessage {...messages.channelName}>
{(msg) => (
<Input
value={this.state.content}
placeholder={msg}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
/>
)}
</FormattedMessage>
<ButtonOutline
onClick={this.handleSubmit}
p={10}
style={{ wordBreak: 'keep-all' }}
>
<FormattedMessage {...messages.change} />
</ButtonOutline>
</Flex>
</Flex>
);
}
}

Channels.propTypes = {
tune: PropTypes.func.isRequired,
};

export default Channels;
37 changes: 37 additions & 0 deletions react-boilerplate/app/containers/Chat/ChatMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import styled from 'styled-components';
import { DarkNicknameLink as NicknameLink } from 'style-store';
import UserAwardPopover from 'components/UserAwardPopover';
import PropTypes from 'prop-types';

const MessageWrapper = styled.div`
border-radius: 5px;
border: 1px solid #006388;
padding: 3px;
margin: 5px 0;
background-color: rgba(255, 255, 255, 0.382);
font-size: 1.1em;
`;

function ChatMessage(props) {
return (
<MessageWrapper>
<NicknameLink to={`/profile/${props.user.rowid}`}>
{props.user.nickname}
</NicknameLink>
<UserAwardPopover
userAward={props.user.currentAward}
style={{ color: '#23527c', fontSize: '0.9em' }}
/>
{' '}
{props.content}
</MessageWrapper>
);
}

ChatMessage.propTypes = {
user: PropTypes.object.isRequired,
content: PropTypes.string.isRequired,
};

export default ChatMessage;
113 changes: 113 additions & 0 deletions react-boilerplate/app/containers/Chat/ChatRoom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';
import PropTypes from 'prop-types';
import bootbox from 'bootbox';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { commitMutation } from 'react-relay';
import environment from 'Environment';
import { FormattedMessage } from 'react-intl';
import { Flex } from 'rebass';
import { Input, ButtonOutline } from 'style-store';
import { CreateMinichatMutation } from 'graphql/CreateMinichatMutation';
import ChatMessage from './ChatMessage';
import Wrapper from './Wrapper';
import { loadMore } from './actions';
import messages from './messages';

const LoadMoreBtn = ButtonOutline.extend`
border-radius: 10px;
padding: 5px;
width: 100%;
`;

const MessageWrapper = Wrapper.extend`
overflow-y: auto;
height: ${(props) => props.height}px;
width: 100%;
margin-bottom: 5px;
border-radius: 0 0 10px 10px;
`;

class ChatRoom extends React.Component {
constructor(props) {
super(props);

this.state = { content: '' };
this.handleChange = (e) => this.setState({ content: e.target.value });
this.handleKeyDown = (e) => {
if (e.key === 'Enter') this.handleSubmit();
};
this.handleSubmit = this.handleSubmit.bind(this);
}

handleSubmit() {
commitMutation(environment, {
mutation: CreateMinichatMutation,
variables: {
input: {
content: this.state.content,
channel: this.props.channel,
},
},
onCompleted: (response, errors) => {
if (errors) {
bootbox.alert(errors.map((e) => e.message).join(','));
}
this.setState({ content: '' });
},
});
}

render() {
return (
<Flex wrap justify="center">
<MessageWrapper height={this.props.height - 50}>
<LoadMoreBtn
onClick={() => this.props.dispatch(loadMore())}
hidden={!this.props.hasPreviousPage}
>
<FormattedMessage {...messages.loadMore} />
</LoadMoreBtn>
{this.props.chatMessages.map((msg) => (
<ChatMessage
user={msg.node.user}
key={msg.node.id}
content={msg.node.content}
/>
))}
</MessageWrapper>
<Flex mx={1} w={1} hidden={this.props.currentUserId === null}>
<Input
value={this.state.content}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
/>
<ButtonOutline
onClick={this.handleSubmit}
p={10}
style={{ wordBreak: 'keep-all' }}
>
<FormattedMessage {...messages.send} />
</ButtonOutline>
</Flex>
</Flex>
);
}
}

ChatRoom.propTypes = {
dispatch: PropTypes.func.isRequired,
chatMessages: PropTypes.array.isRequired,
hasPreviousPage: PropTypes.bool,
channel: PropTypes.string,
height: PropTypes.number.isRequired,
currentUserId: PropTypes.number,
};

const mapDispatchToProps = (dispatch) => ({
dispatch,
});

const withConnect = connect(null, mapDispatchToProps);

export default compose(withConnect)(ChatRoom);
9 changes: 9 additions & 0 deletions react-boilerplate/app/containers/Chat/Wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styled from 'styled-components';

export const Wrapper = styled.div`
border: 2px solid sienna;
border-radius: 10px;
padding: 5px;
`;

export default Wrapper;
Loading

0 comments on commit 495388f

Please sign in to comment.