mirror of
https://github.com/mastodon/mastodon.git
synced 2024-06-01 19:43:29 +00:00
aa2035e2a7
This removes the last usage of the old and deprecated React context API (which will be removed from React 19, and outputs deprecation warnings in React 18.3). Modern context can be used through `contextType` in class components, but it makes them not explicit when consumed (it would have been `this.context.me`, not `this.context.identity.me`). I preferred to switch to injecting an `identity` prop using a wrapper component inspired by `withOptionalRouter`, so consuming identity is explicit (`this.props.identity`).
191 lines
6.2 KiB
JavaScript
191 lines
6.2 KiB
JavaScript
import PropTypes from 'prop-types';
|
|
import { PureComponent } from 'react';
|
|
|
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
|
|
import classNames from 'classnames';
|
|
import { Helmet } from 'react-helmet';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
|
|
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
|
import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/announcements';
|
|
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
|
|
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
|
|
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
|
|
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
|
import { criticalUpdatesPending } from 'mastodon/initial_state';
|
|
|
|
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
|
import { expandHomeTimeline } from '../../actions/timelines';
|
|
import Column from '../../components/column';
|
|
import ColumnHeader from '../../components/column_header';
|
|
import StatusListContainer from '../ui/containers/status_list_container';
|
|
|
|
import { ColumnSettings } from './components/column_settings';
|
|
import { CriticalUpdateBanner } from './components/critical_update_banner';
|
|
|
|
const messages = defineMessages({
|
|
title: { id: 'column.home', defaultMessage: 'Home' },
|
|
show_announcements: { id: 'home.show_announcements', defaultMessage: 'Show announcements' },
|
|
hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' },
|
|
});
|
|
|
|
const mapStateToProps = state => ({
|
|
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
|
|
isPartial: state.getIn(['timelines', 'home', 'isPartial']),
|
|
hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(),
|
|
unreadAnnouncements: state.getIn(['announcements', 'items']).count(item => !item.get('read')),
|
|
showAnnouncements: state.getIn(['announcements', 'show']),
|
|
});
|
|
|
|
class HomeTimeline extends PureComponent {
|
|
static propTypes = {
|
|
identity: identityContextPropShape,
|
|
dispatch: PropTypes.func.isRequired,
|
|
intl: PropTypes.object.isRequired,
|
|
hasUnread: PropTypes.bool,
|
|
isPartial: PropTypes.bool,
|
|
columnId: PropTypes.string,
|
|
multiColumn: PropTypes.bool,
|
|
hasAnnouncements: PropTypes.bool,
|
|
unreadAnnouncements: PropTypes.number,
|
|
showAnnouncements: PropTypes.bool,
|
|
};
|
|
|
|
handlePin = () => {
|
|
const { columnId, dispatch } = this.props;
|
|
|
|
if (columnId) {
|
|
dispatch(removeColumn(columnId));
|
|
} else {
|
|
dispatch(addColumn('HOME', {}));
|
|
}
|
|
};
|
|
|
|
handleMove = (dir) => {
|
|
const { columnId, dispatch } = this.props;
|
|
dispatch(moveColumn(columnId, dir));
|
|
};
|
|
|
|
handleHeaderClick = () => {
|
|
this.column.scrollTop();
|
|
};
|
|
|
|
setRef = c => {
|
|
this.column = c;
|
|
};
|
|
|
|
handleLoadMore = maxId => {
|
|
this.props.dispatch(expandHomeTimeline({ maxId }));
|
|
};
|
|
|
|
componentDidMount () {
|
|
setTimeout(() => this.props.dispatch(fetchAnnouncements()), 700);
|
|
this._checkIfReloadNeeded(false, this.props.isPartial);
|
|
}
|
|
|
|
componentDidUpdate (prevProps) {
|
|
this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial);
|
|
}
|
|
|
|
componentWillUnmount () {
|
|
this._stopPolling();
|
|
}
|
|
|
|
_checkIfReloadNeeded (wasPartial, isPartial) {
|
|
const { dispatch } = this.props;
|
|
|
|
if (wasPartial === isPartial) {
|
|
return;
|
|
} else if (!wasPartial && isPartial) {
|
|
this.polling = setInterval(() => {
|
|
dispatch(expandHomeTimeline());
|
|
}, 3000);
|
|
} else if (wasPartial && !isPartial) {
|
|
this._stopPolling();
|
|
}
|
|
}
|
|
|
|
_stopPolling () {
|
|
if (this.polling) {
|
|
clearInterval(this.polling);
|
|
this.polling = null;
|
|
}
|
|
}
|
|
|
|
handleToggleAnnouncementsClick = (e) => {
|
|
e.stopPropagation();
|
|
this.props.dispatch(toggleShowAnnouncements());
|
|
};
|
|
|
|
render () {
|
|
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
|
|
const pinned = !!columnId;
|
|
const { signedIn } = this.props.identity;
|
|
const banners = [];
|
|
|
|
let announcementsButton;
|
|
|
|
if (hasAnnouncements) {
|
|
announcementsButton = (
|
|
<button
|
|
type='button'
|
|
className={classNames('column-header__button', { 'active': showAnnouncements })}
|
|
title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
|
|
aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
|
|
onClick={this.handleToggleAnnouncementsClick}
|
|
>
|
|
<IconWithBadge id='bullhorn' icon={CampaignIcon} count={unreadAnnouncements} />
|
|
</button>
|
|
);
|
|
}
|
|
|
|
if (criticalUpdatesPending) {
|
|
banners.push(<CriticalUpdateBanner key='critical-update-banner' />);
|
|
}
|
|
|
|
return (
|
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
|
<ColumnHeader
|
|
icon='home'
|
|
iconComponent={HomeIcon}
|
|
active={hasUnread}
|
|
title={intl.formatMessage(messages.title)}
|
|
onPin={this.handlePin}
|
|
onMove={this.handleMove}
|
|
onClick={this.handleHeaderClick}
|
|
pinned={pinned}
|
|
multiColumn={multiColumn}
|
|
extraButton={announcementsButton}
|
|
appendContent={hasAnnouncements && showAnnouncements && <AnnouncementsContainer />}
|
|
>
|
|
<ColumnSettings />
|
|
</ColumnHeader>
|
|
|
|
{signedIn ? (
|
|
<StatusListContainer
|
|
prepend={banners}
|
|
alwaysPrepend
|
|
trackScroll={!pinned}
|
|
scrollKey={`home_timeline-${columnId}`}
|
|
onLoadMore={this.handleLoadMore}
|
|
timelineId='home'
|
|
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up.' />}
|
|
bindToDocument={!multiColumn}
|
|
/>
|
|
) : <NotSignedInIndicator />}
|
|
|
|
<Helmet>
|
|
<title>{intl.formatMessage(messages.title)}</title>
|
|
<meta name='robots' content='noindex' />
|
|
</Helmet>
|
|
</Column>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline)));
|