bigbuffet-rw/app/soapbox/features/ui/index.js

677 lines
24 KiB
JavaScript
Raw Normal View History

2020-03-27 13:59:38 -07:00
'use strict';
import classNames from 'classnames';
import React from 'react';
import { HotKeys } from 'react-hotkeys';
import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
2020-04-14 13:45:38 -07:00
import { Switch, withRouter } from 'react-router-dom';
2020-03-27 13:59:38 -07:00
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
2020-05-28 15:52:07 -07:00
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
2020-03-27 13:59:38 -07:00
import NotificationsContainer from './containers/notifications_container';
import ModalContainer from './containers/modal_container';
import { debounce } from 'lodash';
import { uploadCompose, resetCompose } from '../../actions/compose';
import { expandHomeTimeline } from '../../actions/timelines';
import { expandNotifications } from '../../actions/notifications';
import { fetchReports, fetchUsers, fetchConfig } from '../../actions/admin';
2020-03-27 13:59:38 -07:00
import { fetchFilters } from '../../actions/filters';
import { fetchChats } from 'soapbox/actions/chats';
2020-03-27 13:59:38 -07:00
import { clearHeight } from '../../actions/height_cache';
import { openModal } from '../../actions/modal';
import { fetchFollowRequests } from '../../actions/accounts';
import { fetchScheduledStatuses } from '../../actions/scheduled_statuses';
2020-04-14 13:45:38 -07:00
import { WrappedRoute } from './util/react_router_helpers';
2021-07-01 11:43:14 -07:00
import BundleContainer from './containers/bundle_container';
2020-03-27 13:59:38 -07:00
import UploadArea from './components/upload_area';
import TabsBar from './components/tabs_bar';
2020-05-28 15:52:07 -07:00
import ProfilePage from 'soapbox/pages/profile_page';
// import GroupsPage from 'soapbox/pages/groups_page';
// import GroupPage from 'soapbox/pages/group_page';
2020-04-14 13:45:38 -07:00
// import GroupSidebarPanel from '../groups/sidebar_panel';
2020-05-28 15:52:07 -07:00
import HomePage from 'soapbox/pages/home_page';
import DefaultPage from 'soapbox/pages/default_page';
import EmptyPage from 'soapbox/pages/default_page';
2020-12-30 06:43:24 -08:00
import AdminPage from 'soapbox/pages/admin_page';
import RemoteInstancePage from 'soapbox/pages/remote_instance_page';
2020-03-27 13:59:38 -07:00
import SidebarMenu from '../../components/sidebar_menu';
2020-04-07 18:11:37 -07:00
import { connectUserStream } from '../../actions/streaming';
2020-05-19 07:53:31 -07:00
import { Redirect } from 'react-router-dom';
2020-05-28 15:52:07 -07:00
import Icon from 'soapbox/components/icon';
import { isStaff, isAdmin } from 'soapbox/utils/accounts';
import ProfileHoverCard from 'soapbox/components/profile_hover_card';
2021-03-24 15:53:09 -07:00
import { getAccessToken } from 'soapbox/utils/auth';
2021-08-20 13:46:17 -07:00
import { getFeatures } from 'soapbox/utils/features';
import { fetchCustomEmojis } from 'soapbox/actions/custom_emojis';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
2020-03-27 13:59:38 -07:00
import {
Status,
CommunityTimeline,
PublicTimeline,
2020-12-24 14:20:58 -08:00
RemoteTimeline,
2020-03-27 13:59:38 -07:00
AccountTimeline,
AccountGallery,
HomeTimeline,
Followers,
Following,
Reblogs,
Reactions,
2020-04-14 13:45:38 -07:00
// Favourites,
2020-03-27 13:59:38 -07:00
DirectTimeline,
HashtagTimeline,
Notifications,
FollowRequests,
GenericNotFound,
FavouritedStatuses,
Blocks,
DomainBlocks,
Mutes,
2020-04-25 16:27:37 -07:00
Filters,
2020-03-27 13:59:38 -07:00
PinnedStatuses,
Search,
2020-04-14 13:45:38 -07:00
// Explore,
// Groups,
// GroupTimeline,
2020-03-27 13:59:38 -07:00
ListTimeline,
Lists,
2020-07-29 14:08:36 -07:00
Bookmarks,
2020-04-14 13:45:38 -07:00
// GroupMembers,
// GroupRemovedAccounts,
// GroupCreate,
// GroupEdit,
LoginPage,
ExternalLogin,
Preferences,
2020-04-21 16:00:05 -07:00
EditProfile,
SoapboxConfig,
ExportData,
2020-09-27 09:18:25 -07:00
ImportData,
2021-01-07 12:17:06 -08:00
Backups,
2020-05-24 17:37:15 -07:00
PasswordReset,
2020-06-05 13:00:24 -07:00
SecurityForm,
2020-08-07 13:17:13 -07:00
MfaForm,
ChatIndex,
ChatRoom,
2021-07-01 11:43:14 -07:00
ChatPanes,
ServerInfo,
2020-12-29 10:34:23 -08:00
Dashboard,
2020-12-29 13:55:04 -08:00
AwaitingApproval,
2020-12-31 12:41:43 -08:00
Reports,
2021-01-01 10:50:13 -08:00
ModerationLog,
CryptoDonate,
2021-06-27 11:59:10 -07:00
ScheduledStatuses,
UserIndex,
FederationRestrictions,
Aliases,
FollowRecommendations,
2020-03-27 13:59:38 -07:00
} from './util/async-components';
// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
import '../../components/status';
const isMobile = width => width <= 1190;
2020-03-27 13:59:38 -07:00
const messages = defineMessages({
2020-04-01 13:31:40 -07:00
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave.' },
2020-03-27 13:59:38 -07:00
publish: { id: 'compose_form.publish', defaultMessage: 'Publish' },
});
const mapStateToProps = state => {
2020-04-17 15:14:04 -07:00
const me = state.get('me');
const account = state.getIn(['accounts', me]);
2021-08-20 13:46:17 -07:00
const instance = state.get('instance');
const soapbox = getSoapboxConfig(state);
2020-04-17 15:14:04 -07:00
return {
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
2021-03-24 15:53:09 -07:00
accessToken: getAccessToken(state),
2020-04-07 18:11:37 -07:00
streamingUrl: state.getIn(['instance', 'urls', 'streaming_api']),
2020-04-17 15:14:04 -07:00
me,
account,
2021-08-20 13:46:17 -07:00
features: getFeatures(instance),
soapbox,
2020-04-14 11:44:40 -07:00
};
};
2020-03-27 13:59:38 -07:00
const keyMap = {
help: '?',
new: 'n',
search: 's',
forceNew: 'option+n',
reply: 'r',
favourite: 'f',
react: 'e',
2020-03-27 13:59:38 -07:00
boost: 'b',
mention: 'm',
open: ['enter', 'o'],
openProfile: 'p',
moveDown: ['down', 'j'],
moveUp: ['up', 'k'],
back: 'backspace',
goToHome: 'g h',
goToNotifications: 'g n',
goToFavourites: 'g f',
goToPinned: 'g p',
goToProfile: 'g u',
goToBlocked: 'g b',
goToMuted: 'g m',
goToRequests: 'g r',
toggleHidden: 'x',
toggleSensitive: 'h',
2021-08-28 05:17:14 -07:00
openMedia: 'a',
2020-03-27 13:59:38 -07:00
};
class SwitchingColumnsArea extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
location: PropTypes.object,
onLayoutChange: PropTypes.func.isRequired,
soapbox: ImmutablePropTypes.map.isRequired,
2020-03-27 13:59:38 -07:00
};
state = {
mobile: isMobile(window.innerWidth),
};
componentDidMount() {
2020-03-27 13:59:38 -07:00
window.addEventListener('resize', this.handleResize, { passive: true });
}
componentWillUnmount() {
2020-03-27 13:59:38 -07:00
window.removeEventListener('resize', this.handleResize);
}
handleResize = debounce(() => {
// The cached heights are no longer accurate, invalidate
this.props.onLayoutChange();
this.setState({ mobile: isMobile(window.innerWidth) });
}, 500, {
trailing: true,
});
setRef = c => {
this.node = c.getWrappedInstance();
}
render() {
const { children, soapbox } = this.props;
const authenticatedProfile = soapbox.get('authenticatedProfile');
2020-03-27 13:59:38 -07:00
return (
<Switch>
<WrappedRoute path='/auth/sign_in' component={LoginPage} publicRoute exact />
2020-05-24 17:37:15 -07:00
<WrappedRoute path='/auth/reset_password' component={PasswordReset} publicRoute exact />
<WrappedRoute path='/auth/external' component={ExternalLogin} publicRoute exact />
<WrappedRoute path='/auth/edit' page={DefaultPage} component={SecurityForm} exact />
<WrappedRoute path='/auth/mfa' page={DefaultPage} component={MfaForm} exact />
2020-04-03 18:52:13 -07:00
<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={RemoteInstancePage} component={RemoteTimeline} content={children} />
<WrappedRoute path='/messages' page={DefaultPage} component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
2020-03-27 13:59:38 -07:00
{/*
<WrappedRoute path='/groups' exact page={GroupsPage} component={Groups} content={children} componentParams={{ activeTab: 'featured' }} />
<WrappedRoute path='/groups/create' page={GroupsPage} component={Groups} content={children} componentParams={{ showCreateForm: true, activeTab: 'featured' }} />
<WrappedRoute path='/groups/browse/member' page={GroupsPage} component={Groups} content={children} componentParams={{ activeTab: 'member' }} />
<WrappedRoute path='/groups/browse/admin' page={GroupsPage} component={Groups} content={children} componentParams={{ activeTab: 'admin' }} />
<WrappedRoute path='/groups/:id/members' page={GroupPage} component={GroupMembers} content={children} />
<WrappedRoute path='/groups/:id/removed_accounts' page={GroupPage} component={GroupRemovedAccounts} content={children} />
<WrappedRoute path='/groups/:id/edit' page={GroupPage} component={GroupEdit} content={children} />
<WrappedRoute path='/groups/:id' page={GroupPage} component={GroupTimeline} content={children} />
*/}
{/* Redirects from Pleroma FE, etc. to fix old bookmarks */}
2020-05-19 07:53:31 -07:00
<Redirect from='/main/all' to='/timeline/fediverse' />
<Redirect from='/main/public' to='/timeline/local' />
<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 page={DefaultPage} component={Status} content={children} />
2020-05-19 07:53:31 -07:00
<Redirect from='/users/:username' to='/@:username' />
<Redirect from='/home' to='/' />
{/* Soapbox Legacy redirects */}
<Redirect from='/canary' to='/about/canary' />
<Redirect from='/canary.txt' to='/about/canary' />
2020-03-27 13:59:38 -07:00
<WrappedRoute path='/tags/:id' publicRoute component={HashtagTimeline} content={children} />
<WrappedRoute path='/lists' page={DefaultPage} component={Lists} content={children} />
2021-07-01 17:55:40 -07:00
<WrappedRoute path='/list/:id' page={HomePage} component={ListTimeline} content={children} />
<WrappedRoute path='/bookmarks' page={DefaultPage} component={Bookmarks} content={children} />
2020-03-27 13:59:38 -07:00
<WrappedRoute path='/notifications' page={DefaultPage} component={Notifications} content={children} />
2020-03-27 13:59:38 -07:00
<WrappedRoute path='/search' publicRoute page={DefaultPage} component={Search} content={children} />
<WrappedRoute path='/suggestions' publicRoute page={DefaultPage} component={FollowRecommendations} content={children} />
2020-03-27 13:59:38 -07:00
<WrappedRoute path='/chats' exact page={DefaultPage} component={ChatIndex} content={children} />
<WrappedRoute path='/chats/:chatId' page={DefaultPage} component={ChatRoom} 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} />
2020-03-27 13:59:38 -07:00
<WrappedRoute path='/@:username' publicRoute exact component={AccountTimeline} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/with_replies' publicRoute={!authenticatedProfile} component={AccountTimeline} page={ProfilePage} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/@:username/followers' publicRoute={!authenticatedProfile} component={Followers} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/following' publicRoute={!authenticatedProfile} component={Following} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/media' publicRoute={!authenticatedProfile} component={AccountGallery} page={ProfilePage} content={children} />
2020-03-27 13:59:38 -07:00
<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 page={DefaultPage} component={Status} content={children} />
<WrappedRoute path='/@:username/posts/:statusId/reblogs' page={DefaultPage} component={Reblogs} content={children} />
<WrappedRoute path='/@:username/posts/:statusId/reactions/:reaction?' page={DefaultPage} component={Reactions} content={children} />
2020-03-27 13:59:38 -07:00
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/scheduled_statuses' page={DefaultPage} component={ScheduledStatuses} content={children} />
2020-03-27 13:59:38 -07:00
<Redirect exact from='/settings' to='/settings/preferences' />
<WrappedRoute path='/settings/preferences' page={DefaultPage} component={Preferences} content={children} />
<WrappedRoute path='/settings/profile' page={DefaultPage} component={EditProfile} content={children} />
<WrappedRoute path='/settings/export' page={DefaultPage} component={ExportData} content={children} />
<WrappedRoute path='/settings/import' page={DefaultPage} component={ImportData} content={children} />
<WrappedRoute path='/settings/aliases' page={DefaultPage} component={Aliases} content={children} />
<WrappedRoute path='/backups' page={DefaultPage} component={Backups} content={children} />
<WrappedRoute path='/soapbox/config' page={DefaultPage} component={SoapboxConfig} content={children} />
2020-12-29 10:34:23 -08:00
<Redirect from='/admin/dashboard' to='/admin' exact />
2020-12-30 06:43:24 -08:00
<WrappedRoute path='/admin' page={AdminPage} component={Dashboard} content={children} exact />
<WrappedRoute path='/admin/approval' page={AdminPage} component={AwaitingApproval} content={children} exact />
2020-12-31 12:41:43 -08:00
<WrappedRoute path='/admin/reports' page={AdminPage} component={Reports} content={children} exact />
2021-01-01 10:50:13 -08:00
<WrappedRoute path='/admin/log' page={AdminPage} component={ModerationLog} content={children} exact />
<WrappedRoute path='/admin/users' page={AdminPage} component={UserIndex} content={children} exact />
<WrappedRoute path='/info' page={EmptyPage} component={ServerInfo} content={children} />
<WrappedRoute path='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />
<WrappedRoute path='/federation_restrictions' publicRoute page={DefaultPage} component={FederationRestrictions} content={children} />
2021-06-09 10:38:03 -07:00
<WrappedRoute page={EmptyPage} component={GenericNotFound} content={children} />
2020-03-27 13:59:38 -07:00
</Switch>
);
}
2020-04-14 11:44:40 -07:00
2020-03-27 13:59:38 -07:00
}
export default @connect(mapStateToProps)
@injectIntl
@withRouter
class UI extends React.PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = {
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
location: PropTypes.object,
intl: PropTypes.object.isRequired,
dropdownMenuIsOpen: PropTypes.bool,
2020-04-27 11:56:26 -07:00
me: SoapboxPropTypes.me,
2020-04-14 14:37:17 -07:00
streamingUrl: PropTypes.string,
account: PropTypes.object,
2021-08-20 13:46:17 -07:00
features: PropTypes.object.isRequired,
soapbox: ImmutablePropTypes.map.isRequired,
2020-03-27 13:59:38 -07:00
};
state = {
draggingOver: false,
2020-08-27 14:54:07 -07:00
mobile: isMobile(window.innerWidth),
2020-03-27 13:59:38 -07:00
};
handleLayoutChange = () => {
// The cached heights are no longer accurate, invalidate
this.props.dispatch(clearHeight());
}
handleDragEnter = (e) => {
e.preventDefault();
if (!this.dragTargets) {
this.dragTargets = [];
}
if (this.dragTargets.indexOf(e.target) === -1) {
this.dragTargets.push(e.target);
}
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) {
this.setState({ draggingOver: true });
}
}
handleDragOver = (e) => {
if (this.dataTransferIsText(e.dataTransfer)) return false;
e.preventDefault();
e.stopPropagation();
try {
e.dataTransfer.dropEffect = 'copy';
} catch (err) {
2021-08-03 12:29:36 -07:00
// Do nothing
2020-03-27 13:59:38 -07:00
}
return false;
}
handleDrop = (e) => {
const { me } = this.props;
2020-03-27 13:59:38 -07:00
if (!me) return;
if (this.dataTransferIsText(e.dataTransfer)) return;
e.preventDefault();
this.setState({ draggingOver: false });
this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1) {
this.props.dispatch(uploadCompose(e.dataTransfer.files));
}
}
handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
if (this.dragTargets.length > 0) {
return;
}
this.setState({ draggingOver: false });
}
dataTransferIsText = (dataTransfer) => {
return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1);
}
closeUploadModal = () => {
this.setState({ draggingOver: false });
}
handleServiceWorkerPostMessage = ({ data }) => {
if (data.type === 'navigate') {
this.context.router.history.push(data.path);
} else {
console.warn('Unknown message type:', data.type);
}
}
2020-04-07 18:11:37 -07:00
connectStreaming = (prevProps) => {
const { dispatch } = this.props;
const keys = ['accessToken', 'streamingUrl'];
const credsSet = keys.every(p => this.props[p]);
if (!this.disconnect && credsSet) {
this.disconnect = dispatch(connectUserStream());
}
}
disconnectStreaming = () => {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
}
}
2020-08-27 14:54:07 -07:00
handleResize = debounce(() => {
this.setState({ mobile: isMobile(window.innerWidth) });
}, 500, {
trailing: true,
});
componentDidMount() {
2021-08-20 13:46:17 -07:00
const { account, features } = this.props;
if (!account) return;
2020-03-27 13:59:38 -07:00
window.addEventListener('resize', this.handleResize, { passive: true });
2020-03-27 13:59:38 -07:00
document.addEventListener('dragenter', this.handleDragEnter, false);
document.addEventListener('dragover', this.handleDragOver, false);
document.addEventListener('drop', this.handleDrop, false);
document.addEventListener('dragleave', this.handleDragLeave, false);
document.addEventListener('dragend', this.handleDragEnd, false);
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
}
if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
window.setTimeout(() => Notification.requestPermission(), 120 * 1000);
}
if (account) {
2020-03-27 13:59:38 -07:00
this.props.dispatch(expandHomeTimeline());
this.props.dispatch(expandNotifications());
2021-08-20 13:46:17 -07:00
if (features.chats) {
this.props.dispatch(fetchChats());
}
if (isStaff(account)) {
this.props.dispatch(fetchReports({ state: 'open' }));
2021-07-13 15:27:11 -07:00
this.props.dispatch(fetchUsers(['local', 'need_approval']));
}
if (isAdmin(account)) {
this.props.dispatch(fetchConfig());
}
2020-03-27 13:59:38 -07:00
setTimeout(() => this.props.dispatch(fetchFilters()), 500);
if (account.get('locked')) {
setTimeout(() => this.props.dispatch(fetchFollowRequests()), 700);
}
setTimeout(() => this.props.dispatch(fetchScheduledStatuses()), 900);
2020-03-27 13:59:38 -07:00
}
this.props.dispatch(fetchCustomEmojis());
2020-04-07 18:11:37 -07:00
this.connectStreaming();
}
componentDidUpdate(prevProps) {
this.connectStreaming();
2020-03-27 13:59:38 -07:00
}
componentWillUnmount() {
2020-08-27 14:54:07 -07:00
window.removeEventListener('resize', this.handleResize);
2020-03-27 13:59:38 -07:00
document.removeEventListener('dragenter', this.handleDragEnter);
document.removeEventListener('dragover', this.handleDragOver);
document.removeEventListener('drop', this.handleDrop);
document.removeEventListener('dragleave', this.handleDragLeave);
document.removeEventListener('dragend', this.handleDragEnd);
2020-04-07 18:11:37 -07:00
this.disconnectStreaming();
2020-03-27 13:59:38 -07:00
}
setRef = c => {
this.node = c;
}
handleHotkeyNew = e => {
e.preventDefault();
if (!this.node) return;
2020-03-27 13:59:38 -07:00
const element = this.node.querySelector('.compose-form__autosuggest-wrapper textarea');
if (element) {
element.focus();
}
}
handleHotkeySearch = e => {
e.preventDefault();
if (!this.node) return;
2020-03-27 13:59:38 -07:00
const element = this.node.querySelector('.search__input');
if (element) {
element.focus();
}
}
handleHotkeyForceNew = e => {
this.handleHotkeyNew(e);
this.props.dispatch(resetCompose());
}
handleHotkeyBack = () => {
if (window.history && window.history.length === 1) {
2020-04-10 18:10:39 -07:00
this.context.router.history.push('/');
2020-03-27 13:59:38 -07:00
} else {
this.context.router.history.goBack();
}
}
setHotkeysRef = c => {
const { me } = this.props;
2020-03-27 13:59:38 -07:00
this.hotkeys = c;
2020-04-11 12:41:13 -07:00
if (!me || !this.hotkeys) return;
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
};
2020-03-27 13:59:38 -07:00
}
handleHotkeyToggleHelp = () => {
2020-04-14 11:44:40 -07:00
this.props.dispatch(openModal('HOTKEYS'));
2020-03-27 13:59:38 -07:00
}
handleHotkeyGoToHome = () => {
2020-04-10 18:10:39 -07:00
this.context.router.history.push('/');
2020-03-27 13:59:38 -07:00
}
handleHotkeyGoToNotifications = () => {
this.context.router.history.push('/notifications');
}
handleHotkeyGoToFavourites = () => {
const { account } = this.props;
if (!account) return;
this.context.router.history.push(`/@${account.get('username')}/favorites`);
2020-03-27 13:59:38 -07:00
}
handleHotkeyGoToPinned = () => {
const { account } = this.props;
if (!account) return;
this.context.router.history.push(`/@${account.get('username')}/pins`);
2020-03-27 13:59:38 -07:00
}
handleHotkeyGoToProfile = () => {
const { account } = this.props;
if (!account) return;
this.context.router.history.push(`/@${account.get('username')}`);
2020-03-27 13:59:38 -07:00
}
handleHotkeyGoToBlocked = () => {
this.context.router.history.push('/blocks');
}
handleHotkeyGoToMuted = () => {
this.context.router.history.push('/mutes');
}
handleHotkeyGoToRequests = () => {
this.context.router.history.push('/follow_requests');
}
handleOpenComposeModal = () => {
2020-04-14 11:44:40 -07:00
this.props.dispatch(openModal('COMPOSE'));
2020-03-27 13:59:38 -07:00
}
2020-08-28 12:42:58 -07:00
shouldHideFAB = () => {
const path = this.context.router.history.location.pathname;
return path.match(/^\/posts\/|^\/search|^\/getting-started|^\/chats/);
}
isChatRoomLocation = () => {
const path = this.context.router.history.location.pathname;
return path.match(/^\/chats\/(.*)/);
}
render() {
const { streamingUrl, features, soapbox } = this.props;
2020-08-27 14:54:07 -07:00
const { draggingOver, mobile } = this.state;
const { intl, children, location, dropdownMenuIsOpen, me } = this.props;
2020-03-27 13:59:38 -07:00
// Wait for login to succeed or fail
if (me === null) return null;
// If login didn't fail, wait for streaming to become available
if (me !== false && !streamingUrl) return null;
2020-04-07 18:11:37 -07:00
2020-03-27 13:59:38 -07:00
const handlers = me ? {
help: this.handleHotkeyToggleHelp,
new: this.handleHotkeyNew,
search: this.handleHotkeySearch,
forceNew: this.handleHotkeyForceNew,
back: this.handleHotkeyBack,
goToHome: this.handleHotkeyGoToHome,
goToNotifications: this.handleHotkeyGoToNotifications,
goToFavourites: this.handleHotkeyGoToFavourites,
goToPinned: this.handleHotkeyGoToPinned,
goToProfile: this.handleHotkeyGoToProfile,
goToBlocked: this.handleHotkeyGoToBlocked,
goToMuted: this.handleHotkeyGoToMuted,
goToRequests: this.handleHotkeyGoToRequests,
} : {};
2020-08-28 12:42:58 -07:00
const fabElem = (
<button
key='floating-action-button'
onClick={this.handleOpenComposeModal}
className='floating-action-button'
aria-label={intl.formatMessage(messages.publish)}
>
<Icon id='pencil' fixedWidth />
</button>
);
const floatingActionButton = this.shouldHideFAB() ? null : fabElem;
const classnames = classNames('ui', {
'ui--chatroom': this.isChatRoomLocation(),
});
const style = {
pointerEvents: dropdownMenuIsOpen ? 'none' : null,
};
2020-03-27 13:59:38 -07:00
return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
2020-08-28 12:42:58 -07:00
<div className={classnames} ref={this.setRef} style={style}>
2020-03-27 13:59:38 -07:00
<TabsBar />
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange} soapbox={soapbox}>
2020-03-27 13:59:38 -07:00
{children}
</SwitchingColumnsArea>
{me && floatingActionButton}
<NotificationsContainer />
<ModalContainer />
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
{me && <SidebarMenu />}
2021-08-20 13:46:17 -07:00
{me && features.chats && !mobile && (
2021-07-01 11:43:14 -07:00
<BundleContainer fetchComponent={ChatPanes}>
{Component => <Component />}
</BundleContainer>
)}
<ProfileHoverCard />
2020-03-27 13:59:38 -07:00
</div>
</HotKeys>
);
}
}