Move wrapWidth handling out of MessageBox, improve scroll position handling, use custom routing, close menu when clicking anywhere
This commit is contained in:
parent
a753efd1dd
commit
fec7c93abc
File diff suppressed because one or more lines are too long
@ -41,19 +41,18 @@
|
|||||||
"autolinker": "^1.4.3",
|
"autolinker": "^1.4.3",
|
||||||
"backo": "^1.1.0",
|
"backo": "^1.1.0",
|
||||||
"base64-arraybuffer": "^0.1.5",
|
"base64-arraybuffer": "^0.1.5",
|
||||||
"history": "^4.5.1",
|
"history": "4.5.0",
|
||||||
"immutable": "^3.8.1",
|
"immutable": "^3.8.1",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-dom": "^15.4.2",
|
"react-dom": "^15.4.2",
|
||||||
"react-hot-loader": "next",
|
"react-hot-loader": "next",
|
||||||
"react-redux": "^5.0.2",
|
"react-redux": "^5.0.2",
|
||||||
"react-router": "^3.0.2",
|
|
||||||
"react-router-redux": "^4.0.8",
|
|
||||||
"react-virtualized": "^9.3.0",
|
"react-virtualized": "^9.3.0",
|
||||||
"redux": "^3.6.0",
|
"redux": "^3.6.0",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
"reselect": "^3.0.0"
|
"reselect": "^3.0.0",
|
||||||
|
"url-pattern": "^1.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
|
@ -415,7 +415,7 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
padding: 3px 15px;
|
padding: 4px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-info {
|
.message-info {
|
||||||
@ -525,10 +525,20 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ReactVirtualized__Grid {
|
.ReactVirtualized__List {
|
||||||
|
box-sizing: content-box !important;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rvlist-messages {
|
||||||
|
padding: 7px 0;
|
||||||
|
overflow-y: scroll !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rvlist-users {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.tablist {
|
.tablist {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
|
||||||
import reducer from '../reducers/tab';
|
import reducer from '../reducers/tab';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import { setSelectedTab } from '../actions/tab';
|
import { setSelectedTab } from '../actions/tab';
|
||||||
|
import { locationChanged } from '../util/router';
|
||||||
|
|
||||||
describe('reducers/tab', () => {
|
describe('reducers/tab', () => {
|
||||||
it('sets the tab and adds it to history', () => {
|
it('selects the tab and adds it to history', () => {
|
||||||
let state = reducer(undefined, setSelectedTab('srv', '#chan'));
|
let state = reducer(undefined, setSelectedTab('srv', '#chan'));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toEqual({
|
||||||
@ -90,12 +90,7 @@ describe('reducers/tab', () => {
|
|||||||
it('clears the tab when navigating to a non-tab page', () => {
|
it('clears the tab when navigating to a non-tab page', () => {
|
||||||
let state = reducer(undefined, setSelectedTab('srv', '#chan'));
|
let state = reducer(undefined, setSelectedTab('srv', '#chan'));
|
||||||
|
|
||||||
state = reducer(state, {
|
state = reducer(state, locationChanged('settings'));
|
||||||
type: LOCATION_CHANGE,
|
|
||||||
payload: {
|
|
||||||
pathname: '/settings'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toEqual({
|
||||||
selected: { server: null, name: null },
|
selected: { server: null, name: null },
|
||||||
@ -104,4 +99,20 @@ describe('reducers/tab', () => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('selects the tab and adds it to history when navigating to a tab', () => {
|
||||||
|
const state = reducer(undefined,
|
||||||
|
locationChanged('chat', {
|
||||||
|
server: 'srv',
|
||||||
|
name: '#chan'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(state.toJS()).toEqual({
|
||||||
|
selected: { server: 'srv', name: '#chan' },
|
||||||
|
history: [
|
||||||
|
{ server: 'srv', name: '#chan' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -121,6 +121,7 @@ export function addMessages(messages, server, to, prepend, next) {
|
|||||||
|
|
||||||
if (next) {
|
if (next) {
|
||||||
messages[0].id = next;
|
messages[0].id = next;
|
||||||
|
messages[0].next = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
messages.forEach(message => initMessage(message, server, message.tab || tab, state));
|
messages.forEach(message => initMessage(message, server, message.tab || tab, state));
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import { push, replace } from 'react-router-redux';
|
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
|
import { push, replace } from '../util/router';
|
||||||
|
|
||||||
export function select(server, name) {
|
export function select(server, name) {
|
||||||
const pm = name && name.charAt(0) !== '#';
|
if (name) {
|
||||||
if (pm) {
|
|
||||||
return push(`/${server}/pm/${name}`);
|
|
||||||
} else if (name) {
|
|
||||||
return push(`/${server}/${encodeURIComponent(name)}`);
|
return push(`/${server}/${encodeURIComponent(name)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return push(`/${server}`);
|
return push(`/${server}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,18 +3,19 @@ import { List } from 'react-virtualized/dist/commonjs/List';
|
|||||||
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
|
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
import { measureScrollBarWidth } from '../util';
|
|
||||||
import { getScrollPos, saveScrollPos } from '../util/scrollPosition';
|
import { getScrollPos, saveScrollPos } from '../util/scrollPosition';
|
||||||
|
|
||||||
const scrollBarWidth = measureScrollBarWidth();
|
const fetchThreshold = 100;
|
||||||
const listStyle = { padding: '7px 0', boxSizing: 'content-box' };
|
|
||||||
const threshold = 100;
|
|
||||||
|
|
||||||
export default class MessageBox extends PureComponent {
|
export default class MessageBox extends PureComponent {
|
||||||
componentDidMount() {
|
componentWillMount() {
|
||||||
this.loadScrollPos();
|
this.loadScrollPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.scrollTop = -1;
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps) {
|
componentWillUpdate(nextProps) {
|
||||||
if (nextProps.messages !== this.props.messages) {
|
if (nextProps.messages !== this.props.messages) {
|
||||||
this.list.recomputeRowHeights();
|
this.list.recomputeRowHeights();
|
||||||
@ -22,6 +23,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
|
|
||||||
if (nextProps.tab !== this.props.tab) {
|
if (nextProps.tab !== this.props.tab) {
|
||||||
this.saveScrollPos();
|
this.saveScrollPos();
|
||||||
|
this.bottom = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextProps.messages.get(0) !== this.props.messages.get(0)) {
|
if (nextProps.messages.get(0) !== this.props.messages.get(0)) {
|
||||||
@ -41,7 +43,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (prevProps.tab !== this.props.tab) {
|
if (prevProps.tab !== this.props.tab) {
|
||||||
this.loadScrollPos();
|
this.loadScrollPos(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.nextScrollTop > 0) {
|
if (this.nextScrollTop > 0) {
|
||||||
@ -50,8 +52,6 @@ export default class MessageBox extends PureComponent {
|
|||||||
} else if (this.bottom) {
|
} else if (this.bottom) {
|
||||||
this.list.scrollToRow(this.props.messages.size);
|
this.list.scrollToRow(this.props.messages.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateWidth();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@ -66,7 +66,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return this.props.messages.get(index - 1).height;
|
return this.props.messages.get(index - 1).height;
|
||||||
}
|
};
|
||||||
|
|
||||||
listRef = el => {
|
listRef = el => {
|
||||||
this.list = el;
|
this.list = el;
|
||||||
@ -80,15 +80,22 @@ export default class MessageBox extends PureComponent {
|
|||||||
const { tab } = this.props;
|
const { tab } = this.props;
|
||||||
this.scrollKey = `msg:${tab.server}:${tab.name}`;
|
this.scrollKey = `msg:${tab.server}:${tab.name}`;
|
||||||
return this.scrollKey;
|
return this.scrollKey;
|
||||||
}
|
};
|
||||||
|
|
||||||
loadScrollPos = () => {
|
loadScrollPos = scroll => {
|
||||||
const pos = getScrollPos(this.updateScrollKey());
|
const pos = getScrollPos(this.updateScrollKey());
|
||||||
if (pos >= 0) {
|
if (pos >= 0) {
|
||||||
this.bottom = false;
|
this.bottom = false;
|
||||||
this.container.scrollTop = pos;
|
if (scroll) {
|
||||||
|
this.list.scrollToPosition(pos);
|
||||||
|
} else {
|
||||||
|
this.scrollTop = pos;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.bottom = true;
|
this.bottom = true;
|
||||||
|
if (scroll) {
|
||||||
|
this.list.scrollToRow(this.props.messages.size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,37 +105,6 @@ export default class MessageBox extends PureComponent {
|
|||||||
} else {
|
} else {
|
||||||
saveScrollPos(this.scrollKey, this.container.scrollTop);
|
saveScrollPos(this.scrollKey, this.container.scrollTop);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
scrollDown = () => {
|
|
||||||
this.container.scrollTop = this.container.scrollHeight - this.container.clientHeight;
|
|
||||||
};
|
|
||||||
|
|
||||||
updateWidth = (width) => {
|
|
||||||
const { tab, setWrapWidth, updateMessageHeight } = this.props;
|
|
||||||
let wrapWidth = width || this.width;
|
|
||||||
|
|
||||||
if (width) {
|
|
||||||
if (tab.isChannel() && window.innerWidth > 600) {
|
|
||||||
wrapWidth += 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.width = wrapWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.container.scrollHeight > this.container.clientHeight) {
|
|
||||||
wrapWidth -= scrollBarWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.wrapWidth !== wrapWidth) {
|
|
||||||
this.wrapWidth = wrapWidth;
|
|
||||||
setWrapWidth(wrapWidth);
|
|
||||||
updateMessageHeight();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleResize = size => {
|
|
||||||
this.updateWidth(size.width - 30);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchMore = debounce(() => {
|
fetchMore = debounce(() => {
|
||||||
@ -138,7 +114,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
|
|
||||||
handleScroll = ({ scrollTop, clientHeight, scrollHeight }) => {
|
handleScroll = ({ scrollTop, clientHeight, scrollHeight }) => {
|
||||||
if (this.props.hasMoreMessages &&
|
if (this.props.hasMoreMessages &&
|
||||||
scrollTop <= threshold &&
|
scrollTop <= fetchThreshold &&
|
||||||
scrollTop < this.prevScrollTop &&
|
scrollTop < this.prevScrollTop &&
|
||||||
!this.loading) {
|
!this.loading) {
|
||||||
if (this.mouseDown) {
|
if (this.mouseDown) {
|
||||||
@ -196,13 +172,20 @@ export default class MessageBox extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const props = {};
|
||||||
|
if (this.bottom) {
|
||||||
|
props.scrollToIndex = this.props.messages.size;
|
||||||
|
} else if (this.scrollTop >= 0) {
|
||||||
|
props.scrollTop = this.scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="messagebox"
|
className="messagebox"
|
||||||
onMouseDown={this.handleMouseDown}
|
onMouseDown={this.handleMouseDown}
|
||||||
onMouseUp={this.handleMouseUp}
|
onMouseUp={this.handleMouseUp}
|
||||||
>
|
>
|
||||||
<AutoSizer onResize={this.handleResize}>
|
<AutoSizer>
|
||||||
{({ width, height }) => (
|
{({ width, height }) => (
|
||||||
<List
|
<List
|
||||||
ref={this.listRef}
|
ref={this.listRef}
|
||||||
@ -212,7 +195,8 @@ export default class MessageBox extends PureComponent {
|
|||||||
rowHeight={this.getRowHeight}
|
rowHeight={this.getRowHeight}
|
||||||
rowRenderer={this.renderMessage}
|
rowRenderer={this.renderMessage}
|
||||||
onScroll={this.handleScroll}
|
onScroll={this.handleScroll}
|
||||||
style={listStyle}
|
className="rvlist-messages"
|
||||||
|
{...props}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
|
@ -2,20 +2,9 @@ import React, { PureComponent } from 'react';
|
|||||||
import TabListItem from './TabListItem';
|
import TabListItem from './TabListItem';
|
||||||
|
|
||||||
export default class TabList extends PureComponent {
|
export default class TabList extends PureComponent {
|
||||||
handleTabClick = (server, target) => {
|
handleTabClick = (server, target) => this.props.select(server, target);
|
||||||
this.props.select(server, target);
|
handleConnectClick = () => this.props.pushPath('/connect');
|
||||||
this.props.hideMenu();
|
handleSettingsClick = () => this.props.pushPath('/settings');
|
||||||
};
|
|
||||||
|
|
||||||
handleConnectClick = () => {
|
|
||||||
this.props.pushPath('/connect');
|
|
||||||
this.props.hideMenu();
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSettingsClick = () => {
|
|
||||||
this.props.pushPath('/settings');
|
|
||||||
this.props.hideMenu();
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { tab, channels, servers, privateChats, showTabList } = this.props;
|
const { tab, channels, servers, privateChats, showTabList } = this.props;
|
||||||
|
@ -3,8 +3,6 @@ import { List } from 'react-virtualized/dist/commonjs/List';
|
|||||||
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
|
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
|
||||||
import UserListItem from './UserListItem';
|
import UserListItem from './UserListItem';
|
||||||
|
|
||||||
const listStyle = { padding: '10px 0', boxSizing: 'content-box' };
|
|
||||||
|
|
||||||
export default class UserList extends PureComponent {
|
export default class UserList extends PureComponent {
|
||||||
componentWillUpdate(nextProps) {
|
componentWillUpdate(nextProps) {
|
||||||
if (nextProps.users.size === this.props.users.size) {
|
if (nextProps.users.size === this.props.users.size) {
|
||||||
@ -49,7 +47,7 @@ export default class UserList extends PureComponent {
|
|||||||
rowCount={this.props.users.size}
|
rowCount={this.props.users.size}
|
||||||
rowHeight={24}
|
rowHeight={24}
|
||||||
rowRenderer={this.renderUser}
|
rowRenderer={this.renderUser}
|
||||||
style={listStyle}
|
className="rvlist-users"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
|
@ -1,19 +1,32 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { push } from 'react-router-redux';
|
import { push } from '../util/router';
|
||||||
|
import Route from './Route';
|
||||||
|
import Chat from './Chat';
|
||||||
|
import Connect from './Connect';
|
||||||
|
import Settings from './Settings';
|
||||||
import TabList from '../components/TabList';
|
import TabList from '../components/TabList';
|
||||||
import { select } from '../actions/tab';
|
import { select } from '../actions/tab';
|
||||||
import { hideMenu } from '../actions/ui';
|
import { hideMenu } from '../actions/ui';
|
||||||
|
|
||||||
class App extends PureComponent {
|
class App extends PureComponent {
|
||||||
|
handleClick = () => {
|
||||||
|
if (this.props.showTabList) {
|
||||||
|
this.props.hideMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { showTabList, children } = this.props;
|
const { showTabList } = this.props;
|
||||||
const mainClass = showTabList ? 'main-container off-canvas' : 'main-container';
|
const mainClass = showTabList ? 'main-container off-canvas' : 'main-container';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div onClick={this.handleClick}>
|
||||||
<TabList {...this.props} />
|
<TabList {...this.props} />
|
||||||
<div className={mainClass}>
|
<div className={mainClass}>
|
||||||
{children}
|
<Route name="chat"><Chat /></Route>
|
||||||
|
<Route name="connect"><Connect /></Route>
|
||||||
|
<Route name="settings"><Settings /></Route>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -11,7 +11,7 @@ import UserList from '../components/UserList';
|
|||||||
import { part } from '../actions/channel';
|
import { part } from '../actions/channel';
|
||||||
import { openPrivateChat, closePrivateChat } from '../actions/privateChat';
|
import { openPrivateChat, closePrivateChat } from '../actions/privateChat';
|
||||||
import { searchMessages, toggleSearch } from '../actions/search';
|
import { searchMessages, toggleSearch } from '../actions/search';
|
||||||
import { select, setSelectedTab } from '../actions/tab';
|
import { select } from '../actions/tab';
|
||||||
import { runCommand, sendMessage, updateMessageHeight, fetchMessages } from '../actions/message';
|
import { runCommand, sendMessage, updateMessageHeight, fetchMessages } from '../actions/message';
|
||||||
import { disconnect } from '../actions/server';
|
import { disconnect } from '../actions/server';
|
||||||
import { setWrapWidth, setCharWidth } from '../actions/environment';
|
import { setWrapWidth, setCharWidth } from '../actions/environment';
|
||||||
@ -21,12 +21,6 @@ import * as inputHistoryActions from '../actions/inputHistory';
|
|||||||
import { getSelectedTab } from '../reducers/tab';
|
import { getSelectedTab } from '../reducers/tab';
|
||||||
import { getSelectedMessages } from '../reducers/messages';
|
import { getSelectedMessages } from '../reducers/messages';
|
||||||
|
|
||||||
function updateSelected({ params, dispatch }) {
|
|
||||||
if (params.server) {
|
|
||||||
dispatch(setSelectedTab(params.server, params.channel || params.user));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCharWidth() {
|
function updateCharWidth() {
|
||||||
const charWidth = stringWidth(' ', '16px Roboto Mono, monospace');
|
const charWidth = stringWidth(' ', '16px Roboto Mono, monospace');
|
||||||
window.messageIndent = 6 * charWidth;
|
window.messageIndent = 6 * charWidth;
|
||||||
@ -36,17 +30,7 @@ function updateCharWidth() {
|
|||||||
class Chat extends PureComponent {
|
class Chat extends PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch(updateCharWidth());
|
|
||||||
setTimeout(() => dispatch(updateCharWidth()), 1000);
|
setTimeout(() => dispatch(updateCharWidth()), 1000);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch = phrase => {
|
handleSearch = phrase => {
|
||||||
@ -154,7 +138,10 @@ const titleSelector = createSelector(
|
|||||||
|
|
||||||
const getHasMoreMessages = createSelector(
|
const getHasMoreMessages = createSelector(
|
||||||
getSelectedMessages,
|
getSelectedMessages,
|
||||||
messages => messages.get(0) && typeof messages.get(0).id === 'string'
|
messages => {
|
||||||
|
const first = messages.get(0);
|
||||||
|
return first && first.next;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Router } from 'react-router';
|
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
export default class Root extends Component {
|
export default class Root extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { store, routes, history } = this.props;
|
const { store } = this.props;
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Router routes={routes} history={history} />
|
<App />
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
20
client/src/js/containers/Route.js
Normal file
20
client/src/js/containers/Route.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createStructuredSelector } from 'reselect';
|
||||||
|
|
||||||
|
class Route extends PureComponent {
|
||||||
|
render() {
|
||||||
|
if (this.props.route === this.props.name) {
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRoute = state => state.router.route;
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
route: getRoute
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Route);
|
@ -1,21 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import { browserHistory } from 'react-router';
|
|
||||||
import { syncHistoryWithStore, replace } from 'react-router-redux';
|
|
||||||
import { AppContainer } from 'react-hot-loader';
|
import { AppContainer } from 'react-hot-loader';
|
||||||
import 'react-virtualized/styles.css';
|
import 'react-virtualized/styles.css';
|
||||||
|
|
||||||
import configureStore from './store';
|
import configureStore from './store';
|
||||||
import createRoutes from './routes';
|
import initRouter, { replace } from './util/router';
|
||||||
|
import routes from './routes';
|
||||||
import Socket from './util/Socket';
|
import Socket from './util/Socket';
|
||||||
import handleSocket from './socket';
|
import handleSocket from './socket';
|
||||||
import Root from './containers/Root';
|
import Root from './containers/Root';
|
||||||
import { addMessages } from './actions/message';
|
import { addMessages } from './actions/message';
|
||||||
|
import { initWidthUpdates } from './util/messageHeight';
|
||||||
|
|
||||||
const host = DEV ? `${window.location.hostname}:1337` : window.location.host;
|
const host = DEV ? `${window.location.hostname}:1337` : window.location.host;
|
||||||
const socket = new Socket(host);
|
const socket = new Socket(host);
|
||||||
|
|
||||||
const store = configureStore(socket, browserHistory);
|
const store = configureStore(socket);
|
||||||
|
initRouter(routes, store);
|
||||||
|
handleSocket(socket, store);
|
||||||
|
|
||||||
const env = JSON.parse(document.getElementById('env').innerHTML);
|
const env = JSON.parse(document.getElementById('env').innerHTML);
|
||||||
|
|
||||||
@ -47,20 +49,17 @@ if (env.users) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initWidthUpdates(store);
|
||||||
|
|
||||||
if (env.messages) {
|
if (env.messages) {
|
||||||
const { messages, server, to, next } = env.messages;
|
const { messages, server, to, next } = env.messages;
|
||||||
store.dispatch(addMessages(messages, server, to, false, next));
|
store.dispatch(addMessages(messages, server, to, false, next));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSocket(socket, store);
|
|
||||||
|
|
||||||
const routes = createRoutes();
|
|
||||||
const history = syncHistoryWithStore(browserHistory, store);
|
|
||||||
|
|
||||||
const renderRoot = () => {
|
const renderRoot = () => {
|
||||||
render(
|
render(
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<Root store={store} routes={routes} history={history} />
|
<Root store={store} />
|
||||||
</AppContainer>,
|
</AppContainer>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
@ -69,5 +68,5 @@ const renderRoot = () => {
|
|||||||
renderRoot();
|
renderRoot();
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.accept('./routes', () => renderRoot());
|
module.hot.accept('./containers/Root', () => renderRoot());
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import { routerReducer } from 'react-router-redux';
|
|
||||||
import channels from './channels';
|
import channels from './channels';
|
||||||
import environment from './environment';
|
import environment from './environment';
|
||||||
import input from './input';
|
import input from './input';
|
||||||
@ -11,16 +10,18 @@ import settings from './settings';
|
|||||||
import tab from './tab';
|
import tab from './tab';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
|
|
||||||
export default combineReducers({
|
export default function createReducer(router) {
|
||||||
routing: routerReducer,
|
return combineReducers({
|
||||||
channels,
|
router,
|
||||||
environment,
|
channels,
|
||||||
input,
|
environment,
|
||||||
messages,
|
input,
|
||||||
privateChats,
|
messages,
|
||||||
search,
|
privateChats,
|
||||||
servers,
|
search,
|
||||||
settings,
|
servers,
|
||||||
tab,
|
settings,
|
||||||
ui
|
tab,
|
||||||
});
|
ui
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ const Message = Record({
|
|||||||
time: null,
|
time: null,
|
||||||
type: null,
|
type: null,
|
||||||
channel: false,
|
channel: false,
|
||||||
|
next: false,
|
||||||
height: 0,
|
height: 0,
|
||||||
length: 0,
|
length: 0,
|
||||||
breakpoints: null
|
breakpoints: null
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Record, List } from 'immutable';
|
import { Record, List } from 'immutable';
|
||||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
import { LOCATION_CHANGED } from '../util/router';
|
||||||
import createReducer from '../util/createReducer';
|
import createReducer from '../util/createReducer';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
@ -19,15 +19,17 @@ const State = Record({
|
|||||||
history: List()
|
history: List()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function selectTab(state, action) {
|
||||||
|
const tab = new Tab(action);
|
||||||
|
return state
|
||||||
|
.set('selected', tab)
|
||||||
|
.update('history', history => history.push(tab));
|
||||||
|
}
|
||||||
|
|
||||||
export const getSelectedTab = state => state.tab.selected;
|
export const getSelectedTab = state => state.tab.selected;
|
||||||
|
|
||||||
export default createReducer(new State(), {
|
export default createReducer(new State(), {
|
||||||
[actions.SELECT_TAB](state, action) {
|
[actions.SELECT_TAB]: selectTab,
|
||||||
const tab = new Tab(action);
|
|
||||||
return state
|
|
||||||
.set('selected', tab)
|
|
||||||
.update('history', history => history.push(tab));
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.PART](state, action) {
|
[actions.PART](state, action) {
|
||||||
return state.set('history', state.history.filter(tab =>
|
return state.set('history', state.history.filter(tab =>
|
||||||
@ -45,11 +47,12 @@ export default createReducer(new State(), {
|
|||||||
return state.set('history', state.history.filter(tab => tab.server !== action.server));
|
return state.set('history', state.history.filter(tab => tab.server !== action.server));
|
||||||
},
|
},
|
||||||
|
|
||||||
[LOCATION_CHANGE](state, action) {
|
[LOCATION_CHANGED](state, action) {
|
||||||
if (action.payload.pathname.indexOf('.') === -1 && state.selected.server) {
|
const { route, params } = action;
|
||||||
return state.set('selected', new Tab());
|
if (route === 'chat') {
|
||||||
|
return selectTab(state, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state.set('selected', new Tab());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
import { Record } from 'immutable';
|
import { Record } from 'immutable';
|
||||||
import createReducer from '../util/createReducer';
|
import createReducer from '../util/createReducer';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
|
import { LOCATION_CHANGED } from '../util/router';
|
||||||
|
|
||||||
const State = Record({
|
const State = Record({
|
||||||
showTabList: false,
|
showTabList: false,
|
||||||
showUserList: false
|
showUserList: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function hideMenu(state) {
|
||||||
|
return state.set('showTabList', false);
|
||||||
|
}
|
||||||
|
|
||||||
export default createReducer(new State(), {
|
export default createReducer(new State(), {
|
||||||
[actions.TOGGLE_MENU](state) {
|
[actions.TOGGLE_MENU](state) {
|
||||||
return state.update('showTabList', show => !show);
|
return state.update('showTabList', show => !show);
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.HIDE_MENU](state) {
|
[actions.HIDE_MENU]: hideMenu,
|
||||||
return state.set('showTabList', false);
|
[LOCATION_CHANGED]: hideMenu,
|
||||||
},
|
|
||||||
|
|
||||||
[actions.TOGGLE_USERLIST](state) {
|
[actions.TOGGLE_USERLIST](state) {
|
||||||
return state.update('showUserList', show => !show);
|
return state.update('showUserList', show => !show);
|
||||||
|
@ -1,19 +1,5 @@
|
|||||||
import React from 'react';
|
export default {
|
||||||
import { Route, IndexRoute } from 'react-router';
|
connect: '/connect',
|
||||||
import App from './containers/App';
|
settings: '/settings',
|
||||||
import Connect from './containers/Connect';
|
chat: '/:server(/:name)'
|
||||||
import Chat from './containers/Chat';
|
};
|
||||||
import Settings from './containers/Settings';
|
|
||||||
|
|
||||||
export default function createRoutes() {
|
|
||||||
return (
|
|
||||||
<Route path="/" component={App}>
|
|
||||||
<Route path="connect" component={Connect} />
|
|
||||||
<Route path="settings" component={Settings} />
|
|
||||||
<Route path="/:server" component={Chat} />
|
|
||||||
<Route path="/:server/:channel" component={Chat} />
|
|
||||||
<Route path="/:server/pm/:user" component={Chat} />
|
|
||||||
<IndexRoute component={null} />
|
|
||||||
</Route>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { routeActions } from 'react-router-redux';
|
|
||||||
import { broadcast, inform, addMessage, addMessages } from './actions/message';
|
import { broadcast, inform, addMessage, addMessages } from './actions/message';
|
||||||
import { select } from './actions/tab';
|
import { select } from './actions/tab';
|
||||||
|
import { replace } from './util/router';
|
||||||
import { normalizeChannel } from './util';
|
import { normalizeChannel } from './util';
|
||||||
|
|
||||||
function withReason(message, reason) {
|
function withReason(message, reason) {
|
||||||
@ -52,7 +52,7 @@ export default function handleSocket(socket, { dispatch, getState }) {
|
|||||||
|
|
||||||
servers(data) {
|
servers(data) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
dispatch(routeActions.replace('/connect'));
|
dispatch(replace('/connect'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import { routerMiddleware } from 'react-router-redux';
|
import createReducer from '../reducers';
|
||||||
import reducer from '../reducers';
|
import { routeReducer, routeMiddleware } from '../util/router';
|
||||||
import createSocketMiddleware from '../middleware/socket';
|
import createSocketMiddleware from '../middleware/socket';
|
||||||
import commands from '../commands';
|
import commands from '../commands';
|
||||||
|
|
||||||
export default function configureStore(socket, history) {
|
export default function configureStore(socket) {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
|
||||||
return createStore(reducer, composeEnhancers(
|
const reducer = createReducer(routeReducer);
|
||||||
|
|
||||||
|
const store = createStore(reducer, composeEnhancers(
|
||||||
applyMiddleware(
|
applyMiddleware(
|
||||||
routerMiddleware(history),
|
routeMiddleware,
|
||||||
thunk,
|
thunk,
|
||||||
createSocketMiddleware(socket),
|
createSocketMiddleware(socket),
|
||||||
commands
|
commands
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
return store;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,53 @@
|
|||||||
|
import { stringWidth, measureScrollBarWidth } from './index';
|
||||||
|
import { updateMessageHeight } from '../actions/message';
|
||||||
|
import { setCharWidth, setWrapWidth } from '../actions/environment';
|
||||||
|
|
||||||
const lineHeight = 24;
|
const lineHeight = 24;
|
||||||
let prevWidth;
|
const menuWidth = 200;
|
||||||
|
const userListWidth = 200;
|
||||||
|
const messagePadding = 30;
|
||||||
|
const smallScreen = 600;
|
||||||
let windowWidth;
|
let windowWidth;
|
||||||
|
|
||||||
|
export function initWidthUpdates(store) {
|
||||||
|
const scrollBarWidth = measureScrollBarWidth();
|
||||||
|
|
||||||
|
const charWidth = stringWidth(' ', '16px Roboto Mono, monospace');
|
||||||
|
window.messageIndent = 6 * charWidth;
|
||||||
|
store.dispatch(setCharWidth(charWidth));
|
||||||
|
|
||||||
|
let prevWrapWidth;
|
||||||
|
|
||||||
|
function updateWidth(delta, first) {
|
||||||
|
windowWidth = window.innerWidth;
|
||||||
|
let wrapWidth = windowWidth - scrollBarWidth - messagePadding;
|
||||||
|
if (windowWidth > smallScreen) {
|
||||||
|
wrapWidth -= menuWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrapWidth !== prevWrapWidth) {
|
||||||
|
prevWrapWidth = wrapWidth;
|
||||||
|
|
||||||
|
store.dispatch(setWrapWidth(wrapWidth));
|
||||||
|
if (!first) {
|
||||||
|
store.dispatch(updateMessageHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let resizeRAF;
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
if (resizeRAF) {
|
||||||
|
window.cancelAnimationFrame(resizeRAF);
|
||||||
|
}
|
||||||
|
resizeRAF = window.requestAnimationFrame(updateWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWidth(0, true);
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
}
|
||||||
|
|
||||||
export function findBreakpoints(text) {
|
export function findBreakpoints(text) {
|
||||||
const breakpoints = [];
|
const breakpoints = [];
|
||||||
|
|
||||||
@ -20,17 +66,10 @@ export function findBreakpoints(text) {
|
|||||||
|
|
||||||
export function messageHeight(message, width, charWidth, indent = 0) {
|
export function messageHeight(message, width, charWidth, indent = 0) {
|
||||||
let pad = (6 + (message.from ? message.from.length + 1 : 0)) * charWidth;
|
let pad = (6 + (message.from ? message.from.length + 1 : 0)) * charWidth;
|
||||||
let height = lineHeight + 4;
|
let height = lineHeight + 8;
|
||||||
|
|
||||||
if (message.channel) {
|
if (message.channel && windowWidth > smallScreen) {
|
||||||
if (width !== prevWidth) {
|
width -= userListWidth;
|
||||||
prevWidth = width;
|
|
||||||
windowWidth = window.innerWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowWidth > 600) {
|
|
||||||
width -= 200;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pad + (message.length * charWidth) < width) {
|
if (pad + (message.length * charWidth) < width) {
|
||||||
|
104
client/src/js/util/router.js
Normal file
104
client/src/js/util/router.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import createHistory from 'history/createBrowserHistory';
|
||||||
|
import UrlPattern from 'url-pattern';
|
||||||
|
|
||||||
|
const history = createHistory();
|
||||||
|
|
||||||
|
export const LOCATION_CHANGED = 'ROUTER_LOCATION_CHANGED';
|
||||||
|
export const PUSH = 'ROUTER_PUSH';
|
||||||
|
export const REPLACE = 'ROUTER_REPLACE';
|
||||||
|
|
||||||
|
export function locationChanged(route, params, location) {
|
||||||
|
return {
|
||||||
|
type: LOCATION_CHANGED,
|
||||||
|
route,
|
||||||
|
params,
|
||||||
|
location
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function push(path) {
|
||||||
|
return {
|
||||||
|
type: PUSH,
|
||||||
|
path
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replace(path) {
|
||||||
|
return {
|
||||||
|
type: REPLACE,
|
||||||
|
path
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function routeReducer(state = {}, action) {
|
||||||
|
if (action.type === LOCATION_CHANGED) {
|
||||||
|
return {
|
||||||
|
route: action.route,
|
||||||
|
params: action.params,
|
||||||
|
location: action.location
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function routeMiddleware() {
|
||||||
|
return next => action => {
|
||||||
|
switch (action.type) {
|
||||||
|
case PUSH:
|
||||||
|
history.push(action.path);
|
||||||
|
break;
|
||||||
|
case REPLACE:
|
||||||
|
history.replace(action.path);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return next(action);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function match(routes, location) {
|
||||||
|
let params;
|
||||||
|
for (let i = 0; i < routes.length; i++) {
|
||||||
|
params = routes[i].pattern.match(location.pathname);
|
||||||
|
if (params !== null) {
|
||||||
|
return locationChanged(routes[i].name, params, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decode(location) {
|
||||||
|
location.pathname = decodeURIComponent(location.pathname);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function initRouter(routes, store) {
|
||||||
|
const patterns = [];
|
||||||
|
const opts = {
|
||||||
|
segmentValueCharset: 'a-zA-Z0-9-_.~# %'
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(routes).forEach(name =>
|
||||||
|
patterns.push({
|
||||||
|
name,
|
||||||
|
pattern: new UrlPattern(routes[name], opts)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let matched = match(patterns, decode(history.location));
|
||||||
|
if (matched) {
|
||||||
|
store.dispatch(matched);
|
||||||
|
} else {
|
||||||
|
matched = { location: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
history.listen(location => {
|
||||||
|
const nextMatch = match(patterns, decode(location));
|
||||||
|
if (nextMatch && nextMatch.location.pathname !== matched.location.pathname) {
|
||||||
|
matched = nextMatch;
|
||||||
|
store.dispatch(matched);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -2761,18 +2761,9 @@ hawk@~3.1.3:
|
|||||||
hoek "2.x.x"
|
hoek "2.x.x"
|
||||||
sntp "1.x.x"
|
sntp "1.x.x"
|
||||||
|
|
||||||
history@^3.0.0:
|
history@4.5.0:
|
||||||
version "3.3.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/history/-/history-3.3.0.tgz#fcedcce8f12975371545d735461033579a6dae9c"
|
resolved "https://registry.yarnpkg.com/history/-/history-4.5.0.tgz#7313388109333bf5796fff7407cee1850e8c5061"
|
||||||
dependencies:
|
|
||||||
invariant "^2.2.1"
|
|
||||||
loose-envify "^1.2.0"
|
|
||||||
query-string "^4.2.2"
|
|
||||||
warning "^3.0.0"
|
|
||||||
|
|
||||||
history@^4.5.1:
|
|
||||||
version "4.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/history/-/history-4.6.1.tgz#911cf8eb65728555a94f2b12780a0c531a14d2fd"
|
|
||||||
dependencies:
|
dependencies:
|
||||||
invariant "^2.2.1"
|
invariant "^2.2.1"
|
||||||
loose-envify "^1.2.0"
|
loose-envify "^1.2.0"
|
||||||
@ -2792,7 +2783,7 @@ hoek@2.x.x:
|
|||||||
version "2.16.3"
|
version "2.16.3"
|
||||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||||
|
|
||||||
hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0:
|
hoist-non-react-statics@^1.0.3:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
|
||||||
|
|
||||||
@ -4593,7 +4584,7 @@ promise@^7.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
asap "~2.0.3"
|
asap "~2.0.3"
|
||||||
|
|
||||||
prop-types@^15.0.0, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@~15.5.7:
|
prop-types@^15.0.0, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@~15.5.7:
|
||||||
version "15.5.8"
|
version "15.5.8"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4636,7 +4627,7 @@ qs@6.4.0, qs@~6.4.0:
|
|||||||
version "6.4.0"
|
version "6.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
||||||
|
|
||||||
query-string@^4.1.0, query-string@^4.2.2:
|
query-string@^4.1.0:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
|
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4725,22 +4716,6 @@ react-redux@^5.0.2:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
prop-types "^15.0.0"
|
prop-types "^15.0.0"
|
||||||
|
|
||||||
react-router-redux@^4.0.8:
|
|
||||||
version "4.0.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-router-redux/-/react-router-redux-4.0.8.tgz#227403596b5151e182377dab835b5d45f0f8054e"
|
|
||||||
|
|
||||||
react-router@^3.0.2:
|
|
||||||
version "3.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.0.5.tgz#c3b7873758045a8bbc9562aef4ff4bc8cce7c136"
|
|
||||||
dependencies:
|
|
||||||
create-react-class "^15.5.1"
|
|
||||||
history "^3.0.0"
|
|
||||||
hoist-non-react-statics "^1.2.0"
|
|
||||||
invariant "^2.2.1"
|
|
||||||
loose-envify "^1.2.0"
|
|
||||||
prop-types "^15.5.6"
|
|
||||||
warning "^3.0.0"
|
|
||||||
|
|
||||||
react-virtualized@^9.3.0:
|
react-virtualized@^9.3.0:
|
||||||
version "9.7.4"
|
version "9.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.7.4.tgz#1b5b25c1397282c6ffc651b416befc69e282df12"
|
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.7.4.tgz#1b5b25c1397282c6ffc651b416befc69e282df12"
|
||||||
@ -5561,6 +5536,10 @@ unpipe@1.0.0, unpipe@~1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
|
||||||
|
url-pattern@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/url-pattern/-/url-pattern-1.0.3.tgz#0409292471b24f23c50d65a47931793d2b5acfc1"
|
||||||
|
|
||||||
url@^0.11.0:
|
url@^0.11.0:
|
||||||
version "0.11.0"
|
version "0.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||||
|
Loading…
Reference in New Issue
Block a user