Merge branch 'performance-improvements' into 'develop'
Performance improvements See merge request soapbox-pub/soapbox-fe!568
This commit is contained in:
commit
151f198c66
29 changed files with 458 additions and 438 deletions
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 = (
|
||||
<SettingToggle settings={settings} settingPath={['chats', 'sound']} onChange={this.handleToggleAudio} icons={{ checked: <Icon id='volume-up' />, unchecked: <Icon id='volume-off' /> }} ariaLabel={settings.get('chats', 'sound') === true ? intl.formatMessage(messages.switchToOff) : intl.formatMessage(messages.switchToOn)} />
|
||||
);
|
||||
|
||||
if (showLabel) {
|
||||
toggle = (
|
||||
<SettingToggle settings={settings} settingPath={['chats', 'sound']} onChange={this.handleToggleAudio} icons={{ checked: <Icon id='volume-up' />, unchecked: <Icon id='volume-off' /> }} 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 (
|
||||
<div className='audio-toggle react-toggle--mini'>
|
||||
{toggle}
|
||||
<div className='setting-toggle' aria-label={label}>
|
||||
<Toggle
|
||||
id={id}
|
||||
checked={checked}
|
||||
onChange={this.handleToggleAudio}
|
||||
icons={{ checked: <Icon id='volume-up' />, unchecked: <Icon id='volume-off' /> }}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
{showLabel && (<label htmlFor={id} className='setting-toggle__label'>{label}</label>)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<div className='chat-list'>
|
||||
<div className='chat-list__content'>
|
||||
{chats.count() === 0 &&
|
||||
{chatIds.count() === 0 &&
|
||||
<div className='empty-column-indicator'>{emptyMessage}</div>
|
||||
}
|
||||
{chats.map(chat => (
|
||||
<div key={chat.get('id')} className='chat-list-item'>
|
||||
{chatIds.map(chatId => (
|
||||
<div key={chatId} className='chat-list-item'>
|
||||
<Chat
|
||||
chat={chat}
|
||||
chatId={chatId}
|
||||
onClick={this.props.onClickChat}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 = (
|
||||
<div className={`pane pane--main pane--${mainWindow}`}>
|
||||
<div className={`pane pane--main pane--${mainWindowState}`}>
|
||||
<div className='pane__header'>
|
||||
{unreadCount > 0 && <i className='icon-with-badge__badge'>{shortNumberFormat(unreadCount)}</i>}
|
||||
<button className='pane__title' onClick={this.handleMainWindowToggle}>
|
||||
|
@ -68,10 +52,10 @@ class ChatPanes extends ImmutablePureComponent {
|
|||
<AudioToggle />
|
||||
</div>
|
||||
<div className='pane__content'>
|
||||
<ChatList
|
||||
{open && <ChatList
|
||||
onClickChat={this.handleClickChat}
|
||||
emptyMessage={<FormattedMessage id='chat_panels.main_window.empty' defaultMessage="No chats found. To start a chat, visit a user's profile." />}
|
||||
/>
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -79,9 +63,14 @@ class ChatPanes extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='chat-panes'>
|
||||
{mainWindowPane}
|
||||
{panes.map((pane, i) =>
|
||||
<ChatWindow idx={i} pane={pane} key={pane.get('chat_id')} />,
|
||||
)}
|
||||
{panes.map((pane, i) => (
|
||||
<ChatWindow
|
||||
idx={i}
|
||||
key={pane.get('chat_id')}
|
||||
chatId={pane.get('chat_id')}
|
||||
windowState={pane.get('state')}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<div className={`pane pane--${pane.get('state')}`} style={{ right: `${right}px` }}>
|
||||
<div className={`pane pane--${windowState}`} style={{ right: `${right}px` }}>
|
||||
<div className='pane__header'>
|
||||
{unreadCount > 0 ? unreadIcon : avatar }
|
||||
<button className='pane__title' onClick={this.handleChatToggle(chat.get('id'))}>
|
||||
|
|
|
@ -10,7 +10,7 @@ import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
|||
import AutosuggestInput from '../../../components/autosuggest_input';
|
||||
import PollButtonContainer from '../containers/poll_button_container';
|
||||
import UploadButtonContainer from '../containers/upload_button_container';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||
import MarkdownButtonContainer from '../containers/markdown_button_container';
|
||||
import ScheduleFormContainer from '../containers/schedule_form_container';
|
||||
|
@ -38,8 +38,7 @@ const messages = defineMessages({
|
|||
schedule: { id: 'compose_form.schedule', defaultMessage: 'Schedule' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class ComposeForm extends ImmutablePureComponent {
|
||||
export default class ComposeForm extends ImmutablePureComponent {
|
||||
|
||||
state = {
|
||||
composeFocused: false,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import ComposeForm from '../components/compose_form';
|
||||
import {
|
||||
changeCompose,
|
||||
|
@ -73,4 +74,4 @@ function mergeProps(stateProps, dispatchProps, ownProps) {
|
|||
});
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ComposeForm);
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps, mergeProps)(ComposeForm));
|
||||
|
|
|
@ -4,19 +4,33 @@ import PropTypes from 'prop-types';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import CryptoAddress from './crypto_address';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
// Address example:
|
||||
// {"ticker": "btc", "address": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n", "note": "This is our main address"}
|
||||
const addresses = state.getIn(['soapbox', 'cryptoAddresses']);
|
||||
const { limit } = ownProps;
|
||||
|
||||
return {
|
||||
coinList: typeof limit === 'number' ? addresses.take(limit) : addresses,
|
||||
};
|
||||
const makeGetCoinList = () => {
|
||||
return createSelector(
|
||||
[(addresses, limit) => typeof limit === 'number' ? addresses.take(limit) : addresses],
|
||||
addresses => addresses,
|
||||
);
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
const makeMapStateToProps = () => {
|
||||
const getCoinList = makeGetCoinList();
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
// Address example:
|
||||
// {"ticker": "btc", "address": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n", "note": "This is our main address"}
|
||||
const addresses = state.getIn(['soapbox', 'cryptoAddresses']);
|
||||
const { limit } = ownProps;
|
||||
|
||||
return {
|
||||
coinList: getCoinList(addresses, limit),
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default @connect(makeMapStateToProps)
|
||||
class CoinList extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Toggle from 'react-toggle';
|
||||
|
||||
export default class SettingToggle extends React.PureComponent {
|
||||
export default class SettingToggle extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
prefix: PropTypes.string,
|
||||
|
@ -15,7 +16,6 @@ export default class SettingToggle extends React.PureComponent {
|
|||
PropTypes.bool,
|
||||
PropTypes.object,
|
||||
]),
|
||||
condition: PropTypes.string,
|
||||
ariaLabel: PropTypes.string,
|
||||
}
|
||||
|
||||
|
@ -24,12 +24,18 @@ export default class SettingToggle extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { prefix, settings, settingPath, label, icons, condition, ariaLabel } = this.props;
|
||||
const { prefix, settings, settingPath, label, icons, ariaLabel } = this.props;
|
||||
const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
|
||||
|
||||
return (
|
||||
<div className='setting-toggle' aria-label={ariaLabel}>
|
||||
<Toggle id={id} checked={condition ? settings.getIn(settingPath) === condition : settings.getIn(settingPath)} onChange={this.onChange} icons={icons} onKeyDown={this.onKeyDown} />
|
||||
<Toggle
|
||||
id={id}
|
||||
checked={settings.getIn(settingPath)}
|
||||
onChange={this.onChange}
|
||||
icons={icons}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
{label && (<label htmlFor={id} className='setting-toggle__label'>{label}</label>)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -8,33 +8,34 @@ import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
|||
import { isStaff } from 'soapbox/utils/accounts';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { logOut, switchAccount } from 'soapbox/actions/auth';
|
||||
import { List as ImmutableList, is as ImmutableIs } from 'immutable';
|
||||
import { is as ImmutableIs } from 'immutable';
|
||||
import Avatar from 'soapbox/components/avatar';
|
||||
import DisplayName from 'soapbox/components/display_name';
|
||||
import { makeGetOtherAccounts } from 'soapbox/selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
add: { id: 'profile_dropdown.add_account', defaultMessage: 'Add an existing account' },
|
||||
logout: { id: 'profile_dropdown.logout', defaultMessage: 'Log out @{acct}' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const me = state.get('me');
|
||||
const makeMapStateToProps = () => {
|
||||
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');
|
||||
|
||||
return {
|
||||
account: state.getIn(['accounts', me]),
|
||||
otherAccounts,
|
||||
isStaff: isStaff(state.getIn(['accounts', me])),
|
||||
const accounts = state.get('accounts');
|
||||
const authUsers = state.getIn(['auth', 'users']);
|
||||
const otherAccounts = getOtherAccounts(accounts, authUsers, me);
|
||||
|
||||
return {
|
||||
account: state.getIn(['accounts', me]),
|
||||
otherAccounts,
|
||||
isStaff: isStaff(state.getIn(['accounts', me])),
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
class ProfileDropdown extends React.PureComponent {
|
||||
|
@ -130,4 +131,4 @@ class ProfileDropdown extends React.PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default injectIntl(connect(mapStateToProps)(ProfileDropdown));
|
||||
export default injectIntl(connect(makeMapStateToProps)(ProfileDropdown));
|
||||
|
|
|
@ -1,44 +1,45 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from '../../../components/icon';
|
||||
import SettingToggle from '../../notifications/components/setting_toggle';
|
||||
import Toggle from 'react-toggle';
|
||||
|
||||
const messages = defineMessages({
|
||||
switchToLight: { id: 'tabs_bar.theme_toggle_light', defaultMessage: 'Switch to light theme' },
|
||||
switchToDark: { id: 'tabs_bar.theme_toggle_dark', defaultMessage: 'Switch to dark theme' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class ThemeToggle extends React.PureComponent {
|
||||
export default class ThemeToggle extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
themeMode: PropTypes.string.isRequired,
|
||||
onToggle: PropTypes.func.isRequired,
|
||||
showLabel: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleToggleTheme = () => {
|
||||
this.props.onToggle(this.props.settings.get('themeMode') === 'light' ? 'dark' : 'light');
|
||||
this.props.onToggle(this.props.themeMode === 'light' ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, settings, showLabel } = this.props;
|
||||
let toggle = (
|
||||
<SettingToggle settings={settings} settingPath={['themeMode']} condition={'light'} onChange={this.handleToggleTheme} icons={{ checked: <Icon id='sun' />, unchecked: <Icon id='moon' /> }} ariaLabel={settings.get('themeMode') === 'light' ? intl.formatMessage(messages.switchToDark) : intl.formatMessage(messages.switchToLight)} />
|
||||
);
|
||||
|
||||
if (showLabel) {
|
||||
toggle = (
|
||||
<SettingToggle settings={settings} settingPath={['themeMode']} condition={'light'} onChange={this.handleToggleTheme} icons={{ checked: <Icon id='sun' />, unchecked: <Icon id='moon' /> }} label={settings.get('themeMode') === 'light' ? intl.formatMessage(messages.switchToDark) : intl.formatMessage(messages.switchToLight)} />
|
||||
);
|
||||
}
|
||||
const { intl, themeMode, showLabel } = this.props;
|
||||
const id ='theme-toggle';
|
||||
const label = intl.formatMessage(themeMode === 'light' ? messages.switchToDark : messages.switchToLight);
|
||||
|
||||
return (
|
||||
<div className='theme-toggle'>
|
||||
{toggle}
|
||||
<div className='setting-toggle' aria-label={label}>
|
||||
<Toggle
|
||||
id={id}
|
||||
checked={themeMode === 'light'}
|
||||
onChange={this.handleToggleTheme}
|
||||
icons={{ checked: <Icon id='sun' />, unchecked: <Icon id='moon' /> }}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
{showLabel && (<label htmlFor={id} className='setting-toggle__label'>{label}</label>)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { changeSetting, getSettings } from 'soapbox/actions/settings';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import ThemeToggle from './theme_toggle';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
settings: getSettings(state),
|
||||
themeMode: getSettings(state).get('themeMode'),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -14,4 +15,4 @@ const mapDispatchToProps = (dispatch) => ({
|
|||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ThemeToggle);
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ThemeToggle));
|
||||
|
|
|
@ -21,18 +21,23 @@ const makeGetStatusIds = () => createSelector([
|
|||
});
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { timelineId }) => {
|
||||
const lastStatusId = state.getIn(['timelines', timelineId, 'items'], ImmutableList()).last();
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatusIds = makeGetStatusIds();
|
||||
|
||||
return {
|
||||
statusIds: getStatusIds(state, { type: timelineId }),
|
||||
lastStatusId: lastStatusId,
|
||||
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
|
||||
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
|
||||
hasMore: state.getIn(['timelines', timelineId, 'hasMore']),
|
||||
totalQueuedItemsCount: state.getIn(['timelines', timelineId, 'totalQueuedItemsCount']),
|
||||
const mapStateToProps = (state, { timelineId }) => {
|
||||
const lastStatusId = state.getIn(['timelines', timelineId, 'items'], ImmutableList()).last();
|
||||
|
||||
return {
|
||||
statusIds: getStatusIds(state, { type: timelineId }),
|
||||
lastStatusId: lastStatusId,
|
||||
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
|
||||
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
|
||||
hasMore: state.getIn(['timelines', timelineId, 'hasMore']),
|
||||
totalQueuedItemsCount: state.getIn(['timelines', timelineId, 'totalQueuedItemsCount']),
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
|
@ -47,4 +52,4 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
|||
}, 100),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StatusList);
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList);
|
||||
|
|
|
@ -23,32 +23,24 @@ import { openModal } from '../../actions/modal';
|
|||
import { fetchFollowRequests } from '../../actions/accounts';
|
||||
import { fetchScheduledStatuses } from '../../actions/scheduled_statuses';
|
||||
import { WrappedRoute } from './util/react_router_helpers';
|
||||
import BundleContainer from './containers/bundle_container';
|
||||
import UploadArea from './components/upload_area';
|
||||
import TabsBar from './components/tabs_bar';
|
||||
import LinkFooter from './components/link_footer';
|
||||
import FeaturesPanel from './components/features_panel';
|
||||
import ProfilePage from 'soapbox/pages/profile_page';
|
||||
import UserPanel from './components/user_panel';
|
||||
import WhoToFollowPanel from './components/who_to_follow_panel';
|
||||
import TrendsPanel from './components/trends_panel';
|
||||
import PromoPanel from './components/promo_panel';
|
||||
import FundingPanel from './components/funding_panel';
|
||||
import CryptoDonatePanel from 'soapbox/features/crypto_donate/components/crypto_donate_panel';
|
||||
// import GroupsPage from 'soapbox/pages/groups_page';
|
||||
// import GroupPage from 'soapbox/pages/group_page';
|
||||
// import GroupSidebarPanel from '../groups/sidebar_panel';
|
||||
import HomePage from 'soapbox/pages/home_page';
|
||||
import DefaultPage from 'soapbox/pages/default_page';
|
||||
import EmptyPage from 'soapbox/pages/default_page';
|
||||
import AdminPage from 'soapbox/pages/admin_page';
|
||||
import SidebarMenu from '../../components/sidebar_menu';
|
||||
import { connectUserStream } from '../../actions/streaming';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { isStaff } from 'soapbox/utils/accounts';
|
||||
import ChatPanes from 'soapbox/features/chats/components/chat_panes';
|
||||
import ProfileHoverCard from 'soapbox/components/profile_hover_card';
|
||||
import { getAccessToken } from 'soapbox/utils/auth';
|
||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import {
|
||||
Status,
|
||||
|
@ -95,6 +87,7 @@ import {
|
|||
MfaForm,
|
||||
ChatIndex,
|
||||
ChatRoom,
|
||||
ChatPanes,
|
||||
ServerInfo,
|
||||
Dashboard,
|
||||
AwaitingApproval,
|
||||
|
@ -110,43 +103,6 @@ import '../../components/status';
|
|||
|
||||
const isMobile = width => width <= 1190;
|
||||
|
||||
const makeLayouts = state => {
|
||||
const me = state.get('me');
|
||||
const soapbox = getSoapboxConfig(state);
|
||||
|
||||
const hasPatron = soapbox.getIn(['extensions', 'patron', 'enabled']);
|
||||
const hasCrypto = typeof soapbox.getIn(['cryptoAddresses', 0, 'ticker']) === 'string';
|
||||
const cryptoLimit = soapbox.getIn(['cryptoDonatePanel', 'limit']);
|
||||
const features = getFeatures(state.get('instance'));
|
||||
|
||||
const EMPTY = {
|
||||
LEFT: null,
|
||||
RIGHT: null,
|
||||
};
|
||||
|
||||
const DEFAULT = {
|
||||
LEFT: EMPTY.LEFT,
|
||||
RIGHT: [
|
||||
features.trends && <TrendsPanel limit={3} key='trends-panel' />,
|
||||
features.suggestions && <WhoToFollowPanel limit={5} key='wtf-panel' />,
|
||||
<FeaturesPanel key='features-panel' />,
|
||||
<PromoPanel key='promo-panel' />,
|
||||
<LinkFooter key='link-footer' />,
|
||||
],
|
||||
};
|
||||
|
||||
const HOME = {
|
||||
LEFT: [
|
||||
<UserPanel accountId={me} key='user-panel' />,
|
||||
hasPatron && <FundingPanel key='funding-panel' />,
|
||||
hasCrypto && <CryptoDonatePanel limit={cryptoLimit} key='crypto-panel' />,
|
||||
],
|
||||
RIGHT: DEFAULT.RIGHT,
|
||||
};
|
||||
|
||||
return { EMPTY, DEFAULT, HOME };
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave.' },
|
||||
publish: { id: 'compose_form.publish', defaultMessage: 'Publish' },
|
||||
|
@ -157,15 +113,11 @@ const mapStateToProps = state => {
|
|||
const account = state.getIn(['accounts', me]);
|
||||
|
||||
return {
|
||||
isComposing: state.getIn(['compose', 'is_composing']),
|
||||
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
|
||||
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
|
||||
accessToken: getAccessToken(state),
|
||||
streamingUrl: state.getIn(['instance', 'urls', 'streaming_api']),
|
||||
me,
|
||||
account,
|
||||
layouts: makeLayouts(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -203,7 +155,6 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
children: PropTypes.node,
|
||||
location: PropTypes.object,
|
||||
onLayoutChange: PropTypes.func.isRequired,
|
||||
layouts: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -232,20 +183,20 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { children, layouts: LAYOUT } = this.props;
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<WrappedRoute path='/auth/sign_in' component={LoginPage} publicRoute exact />
|
||||
<WrappedRoute path='/auth/reset_password' component={PasswordReset} publicRoute exact />
|
||||
<WrappedRoute path='/auth/edit' layout={LAYOUT.DEFAULT} component={SecurityForm} exact />
|
||||
<WrappedRoute path='/auth/mfa' layout={LAYOUT.DEFAULT} component={MfaForm} exact />
|
||||
<WrappedRoute path='/auth/edit' page={DefaultPage} component={SecurityForm} exact />
|
||||
<WrappedRoute path='/auth/mfa' page={DefaultPage} component={MfaForm} exact />
|
||||
|
||||
<WrappedRoute path='/' exact page={HomePage} layout={LAYOUT.HOME} component={HomeTimeline} content={children} />
|
||||
<WrappedRoute path='/timeline/local' exact page={HomePage} layout={LAYOUT.HOME} component={CommunityTimeline} content={children} publicRoute />
|
||||
<WrappedRoute path='/timeline/fediverse' exact page={HomePage} layout={LAYOUT.HOME} component={PublicTimeline} content={children} publicRoute />
|
||||
<WrappedRoute path='/timeline/:instance' exact page={HomePage} layout={LAYOUT.HOME} component={RemoteTimeline} content={children} />
|
||||
<WrappedRoute path='/messages' layout={LAYOUT.DEFAULT} component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/' exact page={HomePage} component={HomeTimeline} content={children} />
|
||||
<WrappedRoute path='/timeline/local' exact page={HomePage} component={CommunityTimeline} content={children} publicRoute />
|
||||
<WrappedRoute path='/timeline/fediverse' exact page={HomePage} component={PublicTimeline} content={children} publicRoute />
|
||||
<WrappedRoute path='/timeline/:instance' exact page={HomePage} component={RemoteTimeline} content={children} />
|
||||
<WrappedRoute path='/messages' page={DefaultPage} component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
{/*
|
||||
<WrappedRoute path='/groups' exact page={GroupsPage} component={Groups} content={children} componentParams={{ activeTab: 'featured' }} />
|
||||
|
@ -264,7 +215,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
<Redirect from='/main/friends' to='/' />
|
||||
<Redirect from='/tag/:id' to='/tags/:id' />
|
||||
<Redirect from='/user-settings' to='/settings/profile' />
|
||||
<WrappedRoute path='/notice/:statusId' publicRoute exact layout={LAYOUT.DEFAULT} component={Status} content={children} />
|
||||
<WrappedRoute path='/notice/:statusId' publicRoute exact page={DefaultPage} component={Status} content={children} />
|
||||
<Redirect from='/users/:username' to='/@:username' />
|
||||
<Redirect from='/home' to='/' />
|
||||
|
||||
|
@ -274,22 +225,22 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
|
||||
<WrappedRoute path='/tags/:id' publicRoute component={HashtagTimeline} content={children} />
|
||||
|
||||
<WrappedRoute path='/lists' layout={LAYOUT.DEFAULT} component={Lists} content={children} />
|
||||
<WrappedRoute path='/list/:id' page={HomePage} layout={LAYOUT.DEFAULT} component={ListTimeline} content={children} />
|
||||
<WrappedRoute path='/bookmarks' layout={LAYOUT.DEFAULT} component={Bookmarks} content={children} />
|
||||
<WrappedRoute path='/lists' page={DefaultPage} component={Lists} content={children} />
|
||||
<WrappedRoute path='/list/:id' page={HomePage} component={ListTimeline} content={children} />
|
||||
<WrappedRoute path='/bookmarks' page={DefaultPage} component={Bookmarks} content={children} />
|
||||
|
||||
<WrappedRoute path='/notifications' layout={LAYOUT.DEFAULT} component={Notifications} content={children} />
|
||||
<WrappedRoute path='/notifications' page={DefaultPage} component={Notifications} content={children} />
|
||||
|
||||
<WrappedRoute path='/search' publicRoute layout={LAYOUT.DEFAULT} component={Search} content={children} />
|
||||
<WrappedRoute path='/search' publicRoute page={DefaultPage} component={Search} content={children} />
|
||||
|
||||
<WrappedRoute path='/chats' exact layout={LAYOUT.DEFAULT} component={ChatIndex} content={children} />
|
||||
<WrappedRoute path='/chats/:chatId' layout={LAYOUT.DEFAULT} component={ChatRoom} content={children} />
|
||||
<WrappedRoute path='/chats' exact page={DefaultPage} component={ChatIndex} content={children} />
|
||||
<WrappedRoute path='/chats/:chatId' page={DefaultPage} component={ChatRoom} content={children} />
|
||||
|
||||
<WrappedRoute path='/follow_requests' layout={LAYOUT.DEFAULT} component={FollowRequests} content={children} />
|
||||
<WrappedRoute path='/blocks' layout={LAYOUT.DEFAULT} component={Blocks} content={children} />
|
||||
<WrappedRoute path='/domain_blocks' layout={LAYOUT.DEFAULT} component={DomainBlocks} content={children} />
|
||||
<WrappedRoute path='/mutes' layout={LAYOUT.DEFAULT} component={Mutes} content={children} />
|
||||
<WrappedRoute path='/filters' layout={LAYOUT.DEFAULT} component={Filters} content={children} />
|
||||
<WrappedRoute path='/follow_requests' page={DefaultPage} component={FollowRequests} content={children} />
|
||||
<WrappedRoute path='/blocks' page={DefaultPage} component={Blocks} content={children} />
|
||||
<WrappedRoute path='/domain_blocks' page={DefaultPage} component={DomainBlocks} content={children} />
|
||||
<WrappedRoute path='/mutes' page={DefaultPage} component={Mutes} content={children} />
|
||||
<WrappedRoute path='/filters' page={DefaultPage} component={Filters} content={children} />
|
||||
<WrappedRoute path='/@:username' publicRoute exact component={AccountTimeline} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/with_replies' component={AccountTimeline} page={ProfilePage} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path='/@:username/followers' component={Followers} page={ProfilePage} content={children} />
|
||||
|
@ -298,29 +249,29 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
<WrappedRoute path='/@:username/tagged/:tag' exact component={AccountTimeline} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/favorites' component={FavouritedStatuses} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/pins' component={PinnedStatuses} page={ProfilePage} content={children} />
|
||||
<WrappedRoute path='/@:username/posts/:statusId' publicRoute exact layout={LAYOUT.DEFAULT} component={Status} content={children} />
|
||||
<WrappedRoute path='/@:username/posts/:statusId/reblogs' layout={LAYOUT.DEFAULT} component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/@:username/posts/:statusId' publicRoute exact page={DefaultPage} component={Status} content={children} />
|
||||
<WrappedRoute path='/@:username/posts/:statusId/reblogs' page={DefaultPage} component={Reblogs} content={children} />
|
||||
|
||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/scheduled_statuses' layout={LAYOUT.DEFAULT} component={ScheduledStatuses} content={children} />
|
||||
<WrappedRoute path='/scheduled_statuses' page={DefaultPage} component={ScheduledStatuses} content={children} />
|
||||
|
||||
<Redirect exact from='/settings' to='/settings/preferences' />
|
||||
<WrappedRoute path='/settings/preferences' layout={LAYOUT.DEFAULT} component={Preferences} content={children} />
|
||||
<WrappedRoute path='/settings/profile' layout={LAYOUT.DEFAULT} component={EditProfile} content={children} />
|
||||
<WrappedRoute path='/settings/import' layout={LAYOUT.DEFAULT} component={ImportData} content={children} />
|
||||
<WrappedRoute path='/backups' layout={LAYOUT.DEFAULT} component={Backups} content={children} />
|
||||
<WrappedRoute path='/soapbox/config' layout={LAYOUT.DEFAULT} component={SoapboxConfig} content={children} />
|
||||
<WrappedRoute path='/settings/preferences' page={DefaultPage} component={Preferences} content={children} />
|
||||
<WrappedRoute path='/settings/profile' page={DefaultPage} component={EditProfile} content={children} />
|
||||
<WrappedRoute path='/settings/import' page={DefaultPage} component={ImportData} content={children} />
|
||||
<WrappedRoute path='/backups' page={DefaultPage} component={Backups} content={children} />
|
||||
<WrappedRoute path='/soapbox/config' page={DefaultPage} component={SoapboxConfig} content={children} />
|
||||
|
||||
<Redirect from='/admin/dashboard' to='/admin' exact />
|
||||
<WrappedRoute path='/admin' page={AdminPage} component={Dashboard} content={children} exact />
|
||||
<WrappedRoute path='/admin/approval' page={AdminPage} component={AwaitingApproval} content={children} exact />
|
||||
<WrappedRoute path='/admin/reports' page={AdminPage} component={Reports} content={children} exact />
|
||||
<WrappedRoute path='/admin/log' page={AdminPage} component={ModerationLog} content={children} exact />
|
||||
<WrappedRoute path='/info' layout={LAYOUT.EMPTY} component={ServerInfo} content={children} />
|
||||
<WrappedRoute path='/info' page={EmptyPage} component={ServerInfo} content={children} />
|
||||
|
||||
<WrappedRoute path='/donate/crypto' publicRoute layout={LAYOUT.DEFAULT} component={CryptoDonate} content={children} />
|
||||
<WrappedRoute path='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />
|
||||
|
||||
<WrappedRoute layout={LAYOUT.EMPTY} component={GenericNotFound} content={children} />
|
||||
<WrappedRoute page={EmptyPage} component={GenericNotFound} content={children} />
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
@ -339,16 +290,12 @@ class UI extends React.PureComponent {
|
|||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
isComposing: PropTypes.bool,
|
||||
hasComposingText: PropTypes.bool,
|
||||
hasMediaAttachments: PropTypes.bool,
|
||||
location: PropTypes.object,
|
||||
intl: PropTypes.object.isRequired,
|
||||
dropdownMenuIsOpen: PropTypes.bool,
|
||||
me: SoapboxPropTypes.me,
|
||||
streamingUrl: PropTypes.string,
|
||||
account: PropTypes.object,
|
||||
layouts: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -356,17 +303,6 @@ class UI extends React.PureComponent {
|
|||
mobile: isMobile(window.innerWidth),
|
||||
};
|
||||
|
||||
handleBeforeUnload = (e) => {
|
||||
const { intl, isComposing, hasComposingText, hasMediaAttachments } = this.props;
|
||||
|
||||
if (isComposing && (hasComposingText || hasMediaAttachments)) {
|
||||
// Setting returnValue to any string causes confirmation dialog.
|
||||
// Many browsers no longer display this text to users,
|
||||
// but we set user-friendly message for other browsers, e.g. Edge.
|
||||
e.returnValue = intl.formatMessage(messages.beforeUnload);
|
||||
}
|
||||
}
|
||||
|
||||
handleLayoutChange = () => {
|
||||
// The cached heights are no longer accurate, invalidate
|
||||
this.props.dispatch(clearHeight());
|
||||
|
@ -471,9 +407,8 @@ class UI extends React.PureComponent {
|
|||
componentDidMount() {
|
||||
const { account } = this.props;
|
||||
if (!account) return;
|
||||
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
|
||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||
|
||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||
document.addEventListener('dragenter', this.handleDragEnter, false);
|
||||
document.addEventListener('dragover', this.handleDragOver, false);
|
||||
document.addEventListener('drop', this.handleDrop, false);
|
||||
|
@ -515,7 +450,6 @@ class UI extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
document.removeEventListener('dragenter', this.handleDragEnter);
|
||||
document.removeEventListener('dragover', this.handleDragOver);
|
||||
|
@ -656,7 +590,7 @@ class UI extends React.PureComponent {
|
|||
render() {
|
||||
const { streamingUrl } = this.props;
|
||||
const { draggingOver, mobile } = this.state;
|
||||
const { intl, children, isComposing, location, dropdownMenuIsOpen, me, layouts } = this.props;
|
||||
const { intl, children, location, dropdownMenuIsOpen, me } = this.props;
|
||||
|
||||
if (me === null || !streamingUrl) return null;
|
||||
|
||||
|
@ -692,7 +626,6 @@ class UI extends React.PureComponent {
|
|||
const floatingActionButton = this.shouldHideFAB() ? null : fabElem;
|
||||
|
||||
const classnames = classNames('ui', {
|
||||
'is-composing': isComposing,
|
||||
'ui--chatroom': this.isChatRoomLocation(),
|
||||
});
|
||||
|
||||
|
@ -704,7 +637,7 @@ class UI extends React.PureComponent {
|
|||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
||||
<div className={classnames} ref={this.setRef} style={style}>
|
||||
<TabsBar />
|
||||
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange} layouts={layouts}>
|
||||
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
|
||||
|
@ -715,7 +648,11 @@ class UI extends React.PureComponent {
|
|||
<ModalContainer />
|
||||
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
|
||||
{me && <SidebarMenu />}
|
||||
{me && !mobile && <ChatPanes />}
|
||||
{me && !mobile && (
|
||||
<BundleContainer fetchComponent={ChatPanes}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
<ProfileHoverCard />
|
||||
</div>
|
||||
</HotKeys>
|
||||
|
|
|
@ -210,6 +210,10 @@ export function ChatRoom() {
|
|||
return import(/* webpackChunkName: "features/chats/chat_room" */'../../chats/chat_room');
|
||||
}
|
||||
|
||||
export function ChatPanes() {
|
||||
return import(/* webpackChunkName: "features/chats/components/chat_panes" */'../../chats/components/chat_panes');
|
||||
}
|
||||
|
||||
export function ServerInfo() {
|
||||
return import(/* webpackChunkName: "features/server_info" */'../../server_info');
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import './wdyr';
|
||||
import * as registerPushNotifications from './actions/push_notifications';
|
||||
import { default as Soapbox, store } from './containers/soapbox';
|
||||
import React from 'react';
|
||||
|
|
56
app/soapbox/pages/default_page.js
Normal file
56
app/soapbox/pages/default_page.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import WhoToFollowPanel from 'soapbox/features/ui/components/who_to_follow_panel';
|
||||
import TrendsPanel from 'soapbox/features/ui/components/trends_panel';
|
||||
import PromoPanel from 'soapbox/features/ui/components/promo_panel';
|
||||
import FeaturesPanel from 'soapbox/features/ui/components/features_panel';
|
||||
import LinkFooter from 'soapbox/features/ui/components/link_footer';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const features = getFeatures(state.get('instance'));
|
||||
|
||||
return {
|
||||
showTrendsPanel: features.trends,
|
||||
showWhoToFollowPanel: features.suggestions,
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class DefaultPage extends ImmutablePureComponent {
|
||||
|
||||
render() {
|
||||
const { children, showTrendsPanel, showWhoToFollowPanel } = this.props;
|
||||
|
||||
return (
|
||||
<div className='page'>
|
||||
<div className='page__columns'>
|
||||
<div className='columns-area__panels'>
|
||||
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--left'>
|
||||
<div className='columns-area__panels__pane__inner' />
|
||||
</div>
|
||||
|
||||
<div className='columns-area__panels__main'>
|
||||
<div className='columns-area columns-area--mobile'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--right'>
|
||||
<div className='columns-area__panels__pane__inner'>
|
||||
{showTrendsPanel && <TrendsPanel limit={3} key='trends-panel' />}
|
||||
{showWhoToFollowPanel && <WhoToFollowPanel limit={5} key='wtf-panel' />}
|
||||
<FeaturesPanel key='features-panel' />
|
||||
<PromoPanel key='promo-panel' />
|
||||
<LinkFooter key='link-footer' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
33
app/soapbox/pages/empty_page.js
Normal file
33
app/soapbox/pages/empty_page.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
export default class DefaultPage extends ImmutablePureComponent {
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<div className='page'>
|
||||
<div className='page__columns'>
|
||||
<div className='columns-area__panels'>
|
||||
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--left'>
|
||||
<div className='columns-area__panels__pane__inner' />
|
||||
</div>
|
||||
|
||||
<div className='columns-area__panels__main'>
|
||||
<div className='columns-area columns-area--mobile'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--right'>
|
||||
<div className='columns-area__panels__pane__inner' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,13 +3,34 @@ import { connect } from 'react-redux';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ComposeFormContainer from '../features/compose/containers/compose_form_container';
|
||||
import Avatar from '../components/avatar';
|
||||
import UserPanel from 'soapbox/features/ui/components/user_panel';
|
||||
import WhoToFollowPanel from 'soapbox/features/ui/components/who_to_follow_panel';
|
||||
import TrendsPanel from 'soapbox/features/ui/components/trends_panel';
|
||||
import PromoPanel from 'soapbox/features/ui/components/promo_panel';
|
||||
import FundingPanel from 'soapbox/features/ui/components/funding_panel';
|
||||
import CryptoDonatePanel from 'soapbox/features/crypto_donate/components/crypto_donate_panel';
|
||||
// import GroupSidebarPanel from '../features/groups/sidebar_panel';
|
||||
import FeaturesPanel from 'soapbox/features/ui/components/features_panel';
|
||||
import LinkFooter from 'soapbox/features/ui/components/link_footer';
|
||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const me = state.get('me');
|
||||
const soapbox = getSoapboxConfig(state);
|
||||
const hasPatron = soapbox.getIn(['extensions', 'patron', 'enabled']);
|
||||
const hasCrypto = typeof soapbox.getIn(['cryptoAddresses', 0, 'ticker']) === 'string';
|
||||
const cryptoLimit = soapbox.getIn(['cryptoDonatePanel', 'limit']);
|
||||
const features = getFeatures(state.get('instance'));
|
||||
|
||||
return {
|
||||
me,
|
||||
account: state.getIn(['accounts', me]),
|
||||
showFundingPanel: hasPatron,
|
||||
showCryptoDonatePanel: hasCrypto && cryptoLimit > 0,
|
||||
cryptoLimit,
|
||||
showTrendsPanel: features.trends,
|
||||
showWhoToFollowPanel: features.suggestions,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -21,13 +42,8 @@ class HomePage extends ImmutablePureComponent {
|
|||
this.composeBlock = React.createRef();
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
layout: { LEFT: null, RIGHT: null },
|
||||
}
|
||||
|
||||
render() {
|
||||
const { me, children, account } = this.props;
|
||||
const LAYOUT = this.props.layout || this.defaultProps.layout;
|
||||
const { me, children, account, showFundingPanel, showCryptoDonatePanel, cryptoLimit, showTrendsPanel, showWhoToFollowPanel } = this.props;
|
||||
|
||||
return (
|
||||
<div className='page'>
|
||||
|
@ -36,7 +52,9 @@ class HomePage extends ImmutablePureComponent {
|
|||
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--left'>
|
||||
<div className='columns-area__panels__pane__inner'>
|
||||
{LAYOUT.LEFT}
|
||||
<UserPanel accountId={me} key='user-panel' />
|
||||
{showFundingPanel && <FundingPanel key='funding-panel' />}
|
||||
{showCryptoDonatePanel && <CryptoDonatePanel limit={cryptoLimit} key='crypto-panel' />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -59,7 +77,11 @@ class HomePage extends ImmutablePureComponent {
|
|||
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--right'>
|
||||
<div className='columns-area__panels__pane__inner'>
|
||||
{LAYOUT.RIGHT}
|
||||
{showTrendsPanel && <TrendsPanel limit={3} key='trends-panel' />}
|
||||
{showWhoToFollowPanel && <WhoToFollowPanel limit={5} key='wtf-panel' />}
|
||||
<FeaturesPanel key='features-panel' />
|
||||
<PromoPanel key='promo-panel' />
|
||||
<LinkFooter key='link-footer' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -195,3 +195,18 @@ export const makeGetReport = () => {
|
|||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const makeGetOtherAccounts = () => {
|
||||
return createSelector(
|
||||
[(accounts, authUsers, me) => {
|
||||
return authUsers
|
||||
.keySeq()
|
||||
.reduce((list, id) => {
|
||||
if (id === me) return list;
|
||||
const account = accounts.get(id);
|
||||
return account ? list.push(account) : list;
|
||||
}, ImmutableList());
|
||||
}],
|
||||
otherAccounts => otherAccounts,
|
||||
);
|
||||
};
|
||||
|
|
6
app/soapbox/wdyr.js
Normal file
6
app/soapbox/wdyr.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
||||
whyDidYouRender(React);
|
||||
}
|
|
@ -56,7 +56,6 @@
|
|||
@import 'components/search';
|
||||
@import 'components/react-toggle';
|
||||
@import 'components/getting-started';
|
||||
@import 'components/navigation-bar';
|
||||
@import 'components/promo-panel';
|
||||
@import 'components/drawer';
|
||||
@import 'components/still-image';
|
||||
|
|
|
@ -128,7 +128,6 @@
|
|||
}
|
||||
.account__header__bar { padding: 5px 10px; }
|
||||
|
||||
.navigation-bar,
|
||||
.compose-form {
|
||||
padding: 15px;
|
||||
}
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
.navigation-bar {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
cursor: default;
|
||||
color: var(--primary-text-color--faint);
|
||||
|
||||
strong {
|
||||
color: var(--primary-text-color--faint);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.permalink {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navigation-bar__actions {
|
||||
position: relative;
|
||||
|
||||
.icon-button.close {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
transform: scale(0, 1) translate(-100%, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.compose__action-bar .icon-button {
|
||||
pointer-events: auto;
|
||||
transform: scale(1, 1) translate(0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-bar__profile {
|
||||
flex: 1 1 auto;
|
||||
margin-left: 8px;
|
||||
line-height: 20px;
|
||||
margin-top: -1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.navigation-bar__profile-account {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.navigation-bar__profile-edit {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 630px) and (max-height: 400px) {
|
||||
$duration: 400ms;
|
||||
$delay: 100ms;
|
||||
|
||||
.tabs-bar,
|
||||
.search {
|
||||
will-change: margin-top;
|
||||
transition: margin-top $duration $delay;
|
||||
}
|
||||
|
||||
.navigation-bar {
|
||||
will-change: padding-bottom;
|
||||
transition: padding-bottom $duration $delay;
|
||||
}
|
||||
|
||||
.navigation-bar {
|
||||
& > a:first-child {
|
||||
will-change: margin-top, margin-left, margin-right, width;
|
||||
transition: margin-top $duration $delay, margin-left $duration ($duration + $delay), margin-right $duration ($duration + $delay);
|
||||
}
|
||||
|
||||
& > .navigation-bar__profile-edit {
|
||||
will-change: margin-top;
|
||||
transition: margin-top $duration $delay;
|
||||
}
|
||||
|
||||
.navigation-bar__actions {
|
||||
& > .icon-button.close {
|
||||
will-change: opacity transform;
|
||||
transition: opacity $duration * 0.5 $delay,
|
||||
transform $duration $delay;
|
||||
}
|
||||
|
||||
& > .compose__action-bar .icon-button {
|
||||
will-change: opacity transform;
|
||||
transition: opacity $duration * 0.5 $delay + $duration * 0.5,
|
||||
transform $duration $delay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-composing {
|
||||
.tabs-bar,
|
||||
.search {
|
||||
margin-top: -50px;
|
||||
}
|
||||
|
||||
.navigation-bar {
|
||||
padding-bottom: 0;
|
||||
|
||||
& > a:first-child {
|
||||
margin: -100px 10px 0 -50px;
|
||||
}
|
||||
|
||||
.navigation-bar__profile {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.navigation-bar__profile-edit {
|
||||
position: absolute;
|
||||
margin-top: -60px;
|
||||
}
|
||||
|
||||
.navigation-bar__actions {
|
||||
.icon-button.close {
|
||||
pointer-events: auto;
|
||||
opacity: 1;
|
||||
transform: scale(1, 1) translate(0, 0);
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
.compose__action-bar .icon-button {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transform: scale(0, 1) translate(100%, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,11 +18,6 @@ body.rtl {
|
|||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.navigation-bar__profile {
|
||||
margin-left: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.search__input {
|
||||
padding-right: 10px;
|
||||
padding-left: 30px;
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/runtime": "^7.3.4",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@welldone-software/why-did-you-render": "^6.2.0",
|
||||
"array-includes": "^3.0.3",
|
||||
"autoprefixer": "^10.0.0",
|
||||
"axios": "^0.21.0",
|
||||
|
@ -117,7 +118,7 @@
|
|||
"react-router-scroll-4": "^1.0.0-beta.1",
|
||||
"react-sparklines": "^1.7.0",
|
||||
"react-swipeable-views": "^0.13.0",
|
||||
"react-textarea-autosize": "^8.0.0",
|
||||
"react-textarea-autosize": "^8.3.3",
|
||||
"react-toggle": "^4.0.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux-immutable": "^4.0.0",
|
||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -2135,6 +2135,13 @@
|
|||
"@webassemblyjs/wast-parser" "1.8.5"
|
||||
"@xtuc/long" "4.2.2"
|
||||
|
||||
"@welldone-software/why-did-you-render@^6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-6.2.0.tgz#a053e63f45adb57161c723dee4b005769ea1b64f"
|
||||
integrity sha512-ViwaE09Vgb0yXzyZuGTWCmWy/nBRAEGyztMdFYuxIgmL8yoXX5TVMCfieiJGdRQQPiDUznlYmcu0lu8kN1lwtQ==
|
||||
dependencies:
|
||||
lodash "^4"
|
||||
|
||||
"@xtuc/ieee754@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
||||
|
@ -7768,6 +7775,11 @@ lodash.uniq@^4.5.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
lodash@^4:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
lodash@^4.0.1:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
|
@ -10277,10 +10289,10 @@ react-test-renderer@^16.13.1:
|
|||
react-is "^16.8.6"
|
||||
scheduler "^0.19.1"
|
||||
|
||||
react-textarea-autosize@^8.0.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz#fae38653f5ec172a855fd5fffb39e466d56aebdb"
|
||||
integrity sha512-grajUlVbkx6VdtSxCgzloUIphIZF5bKr21OYMceWPKkniy7H0mRAT/AXPrRtObAe+zUePnNlBwUc4ivVjUGIjw==
|
||||
react-textarea-autosize@^8.3.3:
|
||||
version "8.3.3"
|
||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8"
|
||||
integrity sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.2"
|
||||
use-composed-ref "^1.0.0"
|
||||
|
|
Loading…
Reference in a new issue