Switch to redux and webpack
This commit is contained in:
parent
b247287075
commit
e389454535
97 changed files with 2722 additions and 2656 deletions
34
client/src/js/containers/App.js
Normal file
34
client/src/js/containers/App.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { pushPath } from 'redux-simple-router';
|
||||
import pure from 'pure-render-decorator';
|
||||
import TabList from '../components/TabList';
|
||||
import * as actions from '../actions/tab';
|
||||
|
||||
@pure
|
||||
class App extends Component {
|
||||
render() {
|
||||
const { showMenu, children } = this.props;
|
||||
const mainClass = showMenu ? 'main-container off-canvas' : 'main-container';
|
||||
return (
|
||||
<div>
|
||||
<TabList {...this.props} />
|
||||
<div className={mainClass}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
servers: state.servers,
|
||||
channels: state.channels,
|
||||
privateChats: state.privateChats,
|
||||
showMenu: state.showMenu,
|
||||
selected: state.tab.selected
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { pushPath, ...actions })(App);
|
154
client/src/js/containers/Chat.js
Normal file
154
client/src/js/containers/Chat.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
import React, { Component } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { List, Map } from 'immutable';
|
||||
import pure from 'pure-render-decorator';
|
||||
import ChatTitle from '../components/ChatTitle';
|
||||
import Search from '../components/Search';
|
||||
import MessageBox from '../components/MessageBox';
|
||||
import MessageInput from '../components/MessageInput';
|
||||
import UserList from '../components/UserList';
|
||||
import { part } from '../actions/channel';
|
||||
import { openPrivateChat, closePrivateChat } from '../actions/privateChat';
|
||||
import { searchMessages, toggleSearch } from '../actions/search';
|
||||
import { select, setSelectedChannel, setSelectedUser } from '../actions/tab';
|
||||
import { runCommand, sendMessage } from '../actions/message';
|
||||
import { disconnect } from '../actions/server';
|
||||
import * as inputHistoryActions from '../actions/inputHistory';
|
||||
import { setWrapWidth, setCharWidth } from '../actions/environment';
|
||||
import { stringWidth, wrapMessages } from '../util';
|
||||
|
||||
function updateSelected({ params, dispatch }) {
|
||||
if (params.channel) {
|
||||
dispatch(setSelectedChannel(params.server, params.channel));
|
||||
} else if (params.user) {
|
||||
dispatch(setSelectedUser(params.server, params.user));
|
||||
} else if (params.server) {
|
||||
dispatch(setSelectedChannel(params.server));
|
||||
}
|
||||
}
|
||||
|
||||
function updateCharWidth() {
|
||||
const charWidth = stringWidth(' ', '16px Droid Sans Mono');
|
||||
window.messageIndent = 6 * charWidth;
|
||||
return setCharWidth(charWidth);
|
||||
}
|
||||
|
||||
@pure
|
||||
class Chat extends Component {
|
||||
componentWillMount() {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(updateCharWidth());
|
||||
setTimeout(() => dispatch(updateCharWidth()), 1000);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
updateSelected(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.params.server !== this.props.params.server ||
|
||||
nextProps.params.channel !== this.props.params.channel ||
|
||||
nextProps.params.user !== this.props.params.user) {
|
||||
updateSelected(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { tab, channel, search, history, dispatch } = this.props;
|
||||
|
||||
let chatClass;
|
||||
if (tab.channel) {
|
||||
chatClass = 'chat-channel';
|
||||
} else if (tab.user) {
|
||||
chatClass = 'chat-private';
|
||||
} else {
|
||||
chatClass = 'chat-server';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={chatClass}>
|
||||
<ChatTitle {...this.props } />
|
||||
<Search
|
||||
search={search}
|
||||
onSearch={phrase => tab.channel &&
|
||||
dispatch(searchMessages(tab.server, tab.channel, phrase))}
|
||||
/>
|
||||
<MessageBox {...this.props } />
|
||||
<MessageInput
|
||||
tab={tab}
|
||||
channel={channel}
|
||||
runCommand={this.props.runCommand}
|
||||
sendMessage={this.props.sendMessage}
|
||||
history={history}
|
||||
{...bindActionCreators(inputHistoryActions, dispatch)}
|
||||
/>
|
||||
<UserList {...this.props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const tabSelector = state => state.tab.selected;
|
||||
const messageSelector = state => state.messages;
|
||||
|
||||
const selectedMessagesSelector = createSelector(
|
||||
tabSelector,
|
||||
messageSelector,
|
||||
(tab, messages) => messages.getIn([tab.server, tab.channel || tab.user || tab.server], List())
|
||||
);
|
||||
|
||||
const wrapWidthSelector = state => state.environment.get('wrapWidth');
|
||||
const charWidthSelector = state => state.environment.get('charWidth');
|
||||
|
||||
const wrappedMessagesSelector = createSelector(
|
||||
selectedMessagesSelector,
|
||||
wrapWidthSelector,
|
||||
charWidthSelector,
|
||||
(messages, width, charWidth) => wrapMessages(messages, width, charWidth, 6 * charWidth)
|
||||
);
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const tab = state.tab.selected;
|
||||
const channel = state.channels.getIn([tab.server, tab.channel], Map());
|
||||
|
||||
let title;
|
||||
if (tab.channel) {
|
||||
title = channel.get('name');
|
||||
} else if (tab.user) {
|
||||
title = tab.user;
|
||||
} else {
|
||||
title = state.servers.getIn([tab.server, 'name']);
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
search: state.search,
|
||||
users: channel.get('users', List()),
|
||||
history: state.input.index === -1 ? null : state.input.history.get(state.input.index),
|
||||
messages: wrappedMessagesSelector(state),
|
||||
channel,
|
||||
tab
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
dispatch,
|
||||
...bindActionCreators({
|
||||
select,
|
||||
toggleSearch,
|
||||
searchMessages,
|
||||
runCommand,
|
||||
sendMessage,
|
||||
part,
|
||||
disconnect,
|
||||
openPrivateChat,
|
||||
closePrivateChat,
|
||||
setWrapWidth
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Chat);
|
81
client/src/js/containers/Connect.js
Normal file
81
client/src/js/containers/Connect.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import pure from 'pure-render-decorator';
|
||||
import Navicon from '../components/Navicon';
|
||||
import * as serverActions from '../actions/server';
|
||||
import { join } from '../actions/channel';
|
||||
import { select } from '../actions/tab';
|
||||
|
||||
@pure
|
||||
class Connect extends Component {
|
||||
state = {
|
||||
showOptionals: false
|
||||
}
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { dispatch } = this.props;
|
||||
const address = e.target.address.value.trim();
|
||||
const nick = e.target.nick.value.trim();
|
||||
const channels = e.target.channels.value.split(',').map(s => s.trim()).filter(s => s);
|
||||
const opts = {
|
||||
name: e.target.name.value.trim(),
|
||||
tls: e.target.ssl.checked
|
||||
};
|
||||
|
||||
if (this.state.showOptionals) {
|
||||
opts.realname = e.target.realname.value.trim();
|
||||
opts.username = e.target.username.value.trim();
|
||||
opts.password = e.target.password.value.trim();
|
||||
}
|
||||
|
||||
if (address.indexOf('.') > 0 && nick) {
|
||||
dispatch(serverActions.connect(address, nick, opts));
|
||||
dispatch(select(address));
|
||||
|
||||
if (channels.length > 0) {
|
||||
dispatch(join(channels, address));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleShowClick = () => {
|
||||
this.setState({ showOptionals: !this.state.showOptionals });
|
||||
}
|
||||
|
||||
render() {
|
||||
let optionals = null;
|
||||
|
||||
if (this.state.showOptionals) {
|
||||
optionals = (
|
||||
<div>
|
||||
<input name="username" type="text" placeholder="Username" />
|
||||
<input name="password" type="text" placeholder="Password" />
|
||||
<input name="realname" type="text" placeholder="Realname" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="connect">
|
||||
<Navicon />
|
||||
<form ref="form" className="connect-form" onSubmit={this.handleSubmit}>
|
||||
<h1>Connect</h1>
|
||||
<input name="name" type="text" placeholder="Name" defaultValue="Freenode" />
|
||||
<input name="address" type="text" placeholder="Address" defaultValue="irc.freenode.net" />
|
||||
<input name="nick" type="text" placeholder="Nick" />
|
||||
<input name="channels" type="text" placeholder="Channels" />
|
||||
{optionals}
|
||||
<p>
|
||||
<label><input name="ssl" type="checkbox" />SSL</label>
|
||||
<i className="icon-ellipsis" onClick={this.handleShowClick}></i>
|
||||
</p>
|
||||
<input type="submit" value="Connect" />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(Connect);
|
10
client/src/js/containers/DevTools.js
Normal file
10
client/src/js/containers/DevTools.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
import { createDevTools } from 'redux-devtools';
|
||||
import DockMonitor from 'redux-devtools-dock-monitor';
|
||||
import LogMonitor from 'redux-devtools-log-monitor';
|
||||
|
||||
export default createDevTools(
|
||||
<DockMonitor toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-q">
|
||||
<LogMonitor theme="tomorrow" />
|
||||
</DockMonitor>
|
||||
);
|
20
client/src/js/containers/Root.dev.js
Normal file
20
client/src/js/containers/Root.dev.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Router } from 'react-router';
|
||||
import { Provider } from 'react-redux';
|
||||
import pure from 'pure-render-decorator';
|
||||
import DevTools from './DevTools';
|
||||
|
||||
@pure
|
||||
export default class Root extends Component {
|
||||
render() {
|
||||
const { store, routes, history } = this.props;
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<div>
|
||||
<Router routes={routes} history={history} />
|
||||
<DevTools />
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
5
client/src/js/containers/Root.js
Normal file
5
client/src/js/containers/Root.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
if (__DEV__) {
|
||||
module.exports = require('./Root.dev');
|
||||
} else {
|
||||
module.exports = require('./Root.prod');
|
||||
}
|
16
client/src/js/containers/Root.prod.js
Normal file
16
client/src/js/containers/Root.prod.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Router } from 'react-router';
|
||||
import { Provider } from 'react-redux';
|
||||
import pure from 'pure-render-decorator';
|
||||
|
||||
@pure
|
||||
export default class Root extends Component {
|
||||
render() {
|
||||
const { store, routes, history } = this.props;
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Router routes={routes} history={history} />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue