diff --git a/app/soapbox/components/sidebar_menu.js b/app/soapbox/components/sidebar_menu.js
index 7c20b8d2b..caabe7f0c 100644
--- a/app/soapbox/components/sidebar_menu.js
+++ b/app/soapbox/components/sidebar_menu.js
@@ -13,11 +13,11 @@ import Icon from './icon';
import DisplayName from './display_name';
import { closeSidebar } from '../actions/sidebar';
import { isStaff } from '../utils/accounts';
-import { makeGetAccount } from '../selectors';
+import { makeGetAccount, makeGetOtherAccounts } from '../selectors';
import { logOut, switchAccount } from 'soapbox/actions/auth';
import ThemeToggle from '../features/ui/components/theme_toggle_container';
import { fetchOwnAccounts } from 'soapbox/actions/auth';
-import { List as ImmutableList, is as ImmutableIs } from 'immutable';
+import { is as ImmutableIs } from 'immutable';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
const messages = defineMessages({
@@ -45,29 +45,29 @@ const messages = defineMessages({
add_account: { id: 'profile_dropdown.add_account', defaultMessage: 'Add an existing account' },
});
-const mapStateToProps = state => {
- const me = state.get('me');
+const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
- const soapbox = getSoapboxConfig(state);
+ const getOtherAccounts = makeGetOtherAccounts();
- const otherAccounts =
- state
- .getIn(['auth', 'users'])
- .keySeq()
- .reduce((list, id) => {
- if (id === me) return list;
- const account = state.getIn(['accounts', id]);
- return account ? list.push(account) : list;
- }, ImmutableList());
+ const mapStateToProps = state => {
+ const me = state.get('me');
+ const soapbox = getSoapboxConfig(state);
- return {
- account: getAccount(state, me),
- sidebarOpen: state.get('sidebar').sidebarOpen,
- donateUrl: state.getIn(['patron', 'instance', 'url']),
- hasCrypto: typeof soapbox.getIn(['cryptoAddresses', 0, 'ticker']) === 'string',
- isStaff: isStaff(state.getIn(['accounts', me])),
- otherAccounts,
+ const accounts = state.get('accounts');
+ const authUsers = state.getIn(['auth', 'users']);
+ const otherAccounts = getOtherAccounts(accounts, authUsers, me);
+
+ return {
+ account: getAccount(state, me),
+ sidebarOpen: state.get('sidebar').sidebarOpen,
+ donateUrl: state.getIn(['patron', 'instance', 'url']),
+ hasCrypto: typeof soapbox.getIn(['cryptoAddresses', 0, 'ticker']) === 'string',
+ isStaff: isStaff(state.getIn(['accounts', me])),
+ otherAccounts,
+ };
};
+
+ return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { intl }) => ({
@@ -86,7 +86,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
});
-export default @connect(mapStateToProps, mapDispatchToProps)
+export default @connect(makeMapStateToProps, mapDispatchToProps)
@injectIntl
class SidebarMenu extends ImmutablePureComponent {
diff --git a/app/soapbox/features/chats/components/audio_toggle.js b/app/soapbox/features/chats/components/audio_toggle.js
index 9b207273a..27441e15d 100644
--- a/app/soapbox/features/chats/components/audio_toggle.js
+++ b/app/soapbox/features/chats/components/audio_toggle.js
@@ -2,19 +2,18 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectIntl, defineMessages } from 'react-intl';
-import ImmutablePropTypes from 'react-immutable-proptypes';
import Icon from 'soapbox/components/icon';
import { changeSetting, getSettings } from 'soapbox/actions/settings';
-import SettingToggle from 'soapbox/features/notifications/components/setting_toggle';
+import Toggle from 'react-toggle';
const messages = defineMessages({
- switchToOn: { id: 'chats.audio_toggle_on', defaultMessage: 'Audio notification on' },
- switchToOff: { id: 'chats.audio_toggle_off', defaultMessage: 'Audio notification off' },
+ switchOn: { id: 'chats.audio_toggle_on', defaultMessage: 'Audio notification on' },
+ switchOff: { id: 'chats.audio_toggle_off', defaultMessage: 'Audio notification off' },
});
const mapStateToProps = state => {
return {
- settings: getSettings(state),
+ checked: getSettings(state).getIn(['chats', 'sound'], false),
};
};
@@ -30,30 +29,32 @@ class AudioToggle extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
- settings: ImmutablePropTypes.map.isRequired,
+ checked: PropTypes.bool.isRequired,
toggleAudio: PropTypes.func,
showLabel: PropTypes.bool,
};
handleToggleAudio = () => {
- this.props.toggleAudio(this.props.settings.getIn(['chats', 'sound']) === true ? false : true);
+ this.props.toggleAudio(!this.props.checked);
}
render() {
- const { intl, settings, showLabel } = this.props;
- let toggle = (
- , unchecked: }} ariaLabel={settings.get('chats', 'sound') === true ? intl.formatMessage(messages.switchToOff) : intl.formatMessage(messages.switchToOn)} />
- );
-
- if (showLabel) {
- toggle = (
- , unchecked: }} label={settings.get('chats', 'sound') === true ? intl.formatMessage(messages.switchToOff) : intl.formatMessage(messages.switchToOn)} />
- );
- }
+ const { intl, checked, showLabel } = this.props;
+ const id ='chats-audio-toggle';
+ const label = intl.formatMessage(checked ? messages.switchOff : messages.switchOn);
return (
- {toggle}
+
+ , unchecked: }}
+ onKeyDown={this.onKeyDown}
+ />
+ {showLabel && ()}
+
);
}
diff --git a/app/soapbox/features/chats/components/chat.js b/app/soapbox/features/chats/components/chat.js
index 1a09edd47..8db6ddb62 100644
--- a/app/soapbox/features/chats/components/chat.js
+++ b/app/soapbox/features/chats/components/chat.js
@@ -1,4 +1,5 @@
import React from 'react';
+import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Avatar from '../../../components/avatar';
@@ -6,11 +7,29 @@ import DisplayName from '../../../components/display_name';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { shortNumberFormat } from 'soapbox/utils/numbers';
import emojify from 'soapbox/features/emoji/emoji';
+import { makeGetChat } from 'soapbox/selectors';
-export default class Chat extends ImmutablePureComponent {
+
+const makeMapStateToProps = () => {
+ const getChat = makeGetChat();
+
+ const mapStateToProps = (state, { chatId }) => {
+ const chat = state.getIn(['chats', chatId]);
+
+ return {
+ chat: chat ? getChat(state, chat.toJS()) : undefined,
+ };
+ };
+
+ return mapStateToProps;
+};
+
+export default @connect(makeMapStateToProps)
+class Chat extends ImmutablePureComponent {
static propTypes = {
- chat: ImmutablePropTypes.map.isRequired,
+ chatId: PropTypes.string.isRequired,
+ chat: ImmutablePropTypes.map,
onClick: PropTypes.func,
};
diff --git a/app/soapbox/features/chats/components/chat_list.js b/app/soapbox/features/chats/components/chat_list.js
index e24d31ab9..f89115363 100644
--- a/app/soapbox/features/chats/components/chat_list.js
+++ b/app/soapbox/features/chats/components/chat_list.js
@@ -1,10 +1,18 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Chat from './chat';
-import { makeGetChat } from 'soapbox/selectors';
+import { createSelector } from 'reselect';
+
+const getSortedChatIds = chats => (
+ chats
+ .toList()
+ .sort(chatDateComparator)
+ .map(chat => chat.get('id'))
+);
const chatDateComparator = (chatA, chatB) => {
// Sort most recently updated chats at the top
@@ -17,43 +25,44 @@ const chatDateComparator = (chatA, chatB) => {
return 0;
};
-const mapStateToProps = state => {
- const getChat = makeGetChat();
+const makeMapStateToProps = () => {
+ const sortedChatIdsSelector = createSelector(
+ [getSortedChatIds],
+ chats => chats,
+ );
- const chats = state.get('chats')
- .map(chat => getChat(state, chat.toJS()))
- .toList()
- .sort(chatDateComparator);
+ const mapStateToProps = state => ({
+ chatIds: sortedChatIdsSelector(state.get('chats')),
+ });
- return {
- chats,
- };
+ return mapStateToProps;
};
-export default @connect(mapStateToProps)
+export default @connect(makeMapStateToProps)
@injectIntl
class ChatList extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
+ chatIds: ImmutablePropTypes.list,
onClickChat: PropTypes.func,
emptyMessage: PropTypes.node,
};
render() {
- const { chats, emptyMessage } = this.props;
+ const { chatIds, emptyMessage } = this.props;
return (
- {chats.count() === 0 &&
+ {chatIds.count() === 0 &&
{emptyMessage}
}
- {chats.map(chat => (
-
+ {chatIds.map(chatId => (
+
diff --git a/app/soapbox/features/chats/components/chat_message_list.js b/app/soapbox/features/chats/components/chat_message_list.js
index b51915ae6..426075504 100644
--- a/app/soapbox/features/chats/components/chat_message_list.js
+++ b/app/soapbox/features/chats/components/chat_message_list.js
@@ -14,6 +14,7 @@ import { MediaGallery } from 'soapbox/features/ui/util/async-components';
import Bundle from 'soapbox/features/ui/components/bundle';
import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
import { initReportById } from 'soapbox/actions/reports';
+import { createSelector } from 'reselect';
const messages = defineMessages({
today: { id: 'chats.dividers.today', defaultMessage: 'Today' },
@@ -38,15 +39,34 @@ const makeEmojiMap = record => record.get('emojis', ImmutableList()).reduce((map
return map.set(`:${emoji.get('shortcode')}:`, emoji);
}, ImmutableMap());
-const mapStateToProps = (state, { chatMessageIds }) => ({
- me: state.get('me'),
- chatMessages: chatMessageIds.reduce((acc, curr) => {
- const chatMessage = state.getIn(['chat_messages', curr]);
- return chatMessage ? acc.push(chatMessage) : acc;
- }, ImmutableList()),
-});
+const makeGetChatMessages = () => {
+ return createSelector(
+ [(chatMessages, chatMessageIds) => (
+ chatMessageIds.reduce((acc, curr) => {
+ const chatMessage = chatMessages.get(curr);
+ return chatMessage ? acc.push(chatMessage) : acc;
+ }, ImmutableList())
+ )],
+ chatMessages => chatMessages,
+ );
+};
-export default @connect(mapStateToProps)
+const makeMapStateToProps = () => {
+ const getChatMessages = makeGetChatMessages();
+
+ const mapStateToProps = (state, { chatMessageIds }) => {
+ const chatMessages = state.get('chat_messages');
+
+ return {
+ me: state.get('me'),
+ chatMessages: getChatMessages(chatMessages, chatMessageIds),
+ };
+ };
+
+ return mapStateToProps;
+};
+
+export default @connect(makeMapStateToProps)
@injectIntl
class ChatMessageList extends ImmutablePureComponent {
diff --git a/app/soapbox/features/chats/components/chat_panes.js b/app/soapbox/features/chats/components/chat_panes.js
index 46e3e4260..b1672c127 100644
--- a/app/soapbox/features/chats/components/chat_panes.js
+++ b/app/soapbox/features/chats/components/chat_panes.js
@@ -2,47 +2,32 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import { injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { getSettings } from 'soapbox/actions/settings';
import ChatList from './chat_list';
import { FormattedMessage } from 'react-intl';
-import { makeGetChat } from 'soapbox/selectors';
import { openChat, toggleMainWindow } from 'soapbox/actions/chats';
import ChatWindow from './chat_window';
import { shortNumberFormat } from 'soapbox/utils/numbers';
import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
-import { List as ImmutableList } from 'immutable';
-
-const addChatsToPanes = (state, panesData) => {
- const getChat = makeGetChat();
-
- const newPanes = panesData.get('panes').reduce((acc, pane) => {
- const chat = getChat(state, { id: pane.get('chat_id') });
- if (!chat) return acc;
- return acc.push(pane.set('chat', chat));
- }, ImmutableList());
-
- return panesData.set('panes', newPanes);
-};
const mapStateToProps = state => {
- const panesData = getSettings(state).get('chats');
+ const settings = getSettings(state);
return {
- panesData: addChatsToPanes(state, panesData),
+ panes: settings.getIn(['chats', 'panes']),
+ mainWindowState: settings.getIn(['chats', 'mainWindow']),
unreadCount: state.get('chats').reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0),
};
};
export default @connect(mapStateToProps)
-@injectIntl
class ChatPanes extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- panesData: ImmutablePropTypes.map,
+ mainWindowState: PropTypes.string,
+ panes: ImmutablePropTypes.list,
}
handleClickChat = (chat) => {
@@ -54,12 +39,11 @@ class ChatPanes extends ImmutablePureComponent {
}
render() {
- const { panesData, unreadCount } = this.props;
- const panes = panesData.get('panes');
- const mainWindow = panesData.get('mainWindow');
+ const { panes, mainWindowState, unreadCount } = this.props;
+ const open = mainWindowState === 'open';
const mainWindowPane = (
-
+
{unreadCount > 0 &&
{shortNumberFormat(unreadCount)}}
- }
- />
+ />}
);
@@ -79,9 +63,14 @@ class ChatPanes extends ImmutablePureComponent {
return (
{mainWindowPane}
- {panes.map((pane, i) =>
- ,
- )}
+ {panes.map((pane, i) => (
+
+ ))}
);
}
diff --git a/app/soapbox/features/chats/components/chat_window.js b/app/soapbox/features/chats/components/chat_window.js
index d23c27cad..95ba86e64 100644
--- a/app/soapbox/features/chats/components/chat_window.js
+++ b/app/soapbox/features/chats/components/chat_window.js
@@ -16,21 +16,33 @@ import ChatBox from './chat_box';
import { shortNumberFormat } from 'soapbox/utils/numbers';
import { displayFqn } from 'soapbox/utils/state';
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
+import { makeGetChat } from 'soapbox/selectors';
-const mapStateToProps = (state, { pane }) => ({
- me: state.get('me'),
- chat: state.getIn(['chats', pane.get('chat_id')]),
- displayFqn: displayFqn(state),
-});
+const makeMapStateToProps = () => {
+ const getChat = makeGetChat();
-export default @connect(mapStateToProps)
+ const mapStateToProps = (state, { chatId }) => {
+ const chat = state.getIn(['chats', chatId]);
+
+ return {
+ me: state.get('me'),
+ chat: chat ? getChat(state, chat.toJS()) : undefined,
+ displayFqn: displayFqn(state),
+ };
+ };
+
+ return mapStateToProps;
+};
+
+export default @connect(makeMapStateToProps)
@injectIntl
class ChatWindow extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
- pane: ImmutablePropTypes.map.isRequired,
+ chatId: PropTypes.string.isRequired,
+ windowState: PropTypes.string.isRequired,
idx: PropTypes.number,
chat: ImmutablePropTypes.map,
me: PropTypes.node,
@@ -68,17 +80,17 @@ class ChatWindow extends ImmutablePureComponent {
}
componentDidUpdate(prevProps) {
- const oldState = prevProps.pane.get('state');
- const newState = this.props.pane.get('state');
+ const oldState = prevProps.windowState;
+ const newState = this.props.windowState;
if (oldState !== newState && newState === 'open')
this.focusInput();
}
render() {
- const { pane, idx, chat, displayFqn } = this.props;
- const account = pane.getIn(['chat', 'account']);
- if (!chat || !account) return null;
+ const { windowState, idx, chat, displayFqn } = this.props;
+ if (!chat) return null;
+ const account = chat.get('account');
const right = (285 * (idx + 1)) + 20;
const unreadCount = chat.get('unread');
@@ -98,7 +110,7 @@ class ChatWindow extends ImmutablePureComponent {
);
return (
-
+
{unreadCount > 0 ? unreadIcon : avatar }