0
0
Fork 0

Upgrade to react-router v5 (#25047)

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Renaud Chaput 2023-10-19 19:44:55 +02:00 committed by GitHub
parent 126cd7705d
commit 1b70d7ed7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 419 additions and 361 deletions

View file

@ -4,29 +4,28 @@ import { createPortal } from 'react-dom';
import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import { Icon } from 'mastodon/components/icon';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
export default class ColumnBackButton extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
class ColumnBackButton extends PureComponent {
static propTypes = {
multiColumn: PropTypes.bool,
onClick: PropTypes.func,
...WithRouterPropTypes,
};
handleClick = () => {
const { router } = this.context;
const { onClick } = this.props;
const { onClick, history } = this.props;
if (onClick) {
onClick();
} else if (router.history.location?.state?.fromMastodon) {
router.history.goBack();
} else if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
router.history.push('/');
history.push('/');
}
};
@ -60,3 +59,5 @@ export default class ColumnBackButton extends PureComponent {
}
}
export default withRouter(ColumnBackButton);

View file

@ -5,8 +5,10 @@ import { createPortal } from 'react-dom';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import { Icon } from 'mastodon/components/icon';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
@ -18,7 +20,6 @@ const messages = defineMessages({
class ColumnHeader extends PureComponent {
static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object,
};
@ -38,6 +39,7 @@ class ColumnHeader extends PureComponent {
onClick: PropTypes.func,
appendContent: PropTypes.node,
collapseIssues: PropTypes.bool,
...WithRouterPropTypes,
};
state = {
@ -63,12 +65,12 @@ class ColumnHeader extends PureComponent {
};
handleBackClick = () => {
const { router } = this.context;
const { history } = this.props;
if (router.history.location?.state?.fromMastodon) {
router.history.goBack();
if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
router.history.push('/');
history.push('/');
}
};
@ -78,15 +80,14 @@ class ColumnHeader extends PureComponent {
handlePin = () => {
if (!this.props.pinned) {
this.context.router.history.replace('/');
this.props.history.replace('/');
}
this.props.onPin();
};
render () {
const { router } = this.context;
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props;
const { collapsed, animating } = this.state;
const wrapperClassName = classNames('column-header__wrapper', {
@ -129,7 +130,7 @@ class ColumnHeader extends PureComponent {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
}
if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) {
if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) {
backButton = (
<button onClick={this.handleBackClick} className='column-header__back-button'>
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
@ -215,4 +216,4 @@ class ColumnHeader extends PureComponent {
}
export default injectIntl(ColumnHeader);
export default injectIntl(withRouter(ColumnHeader));

View file

@ -2,13 +2,16 @@ import PropTypes from 'prop-types';
import { PureComponent, cloneElement, Children } from 'react';
import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay';
import { CircularProgress } from "./circular_progress";
import { CircularProgress } from 'mastodon/components/circular_progress';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { IconButton } from './icon_button';
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
@ -16,10 +19,6 @@ let id = 0;
class DropdownMenu extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired,
loading: PropTypes.bool,
@ -159,11 +158,7 @@ class DropdownMenu extends PureComponent {
}
export default class Dropdown extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
class Dropdown extends PureComponent {
static propTypes = {
children: PropTypes.node,
@ -183,6 +178,7 @@ export default class Dropdown extends PureComponent {
renderItem: PropTypes.func,
renderHeader: PropTypes.func,
onItemClick: PropTypes.func,
...WithRouterPropTypes
};
static defaultProps = {
@ -250,7 +246,7 @@ export default class Dropdown extends PureComponent {
item.action();
} else if (item && item.to) {
e.preventDefault();
this.context.router.history.push(item.to);
this.props.history.push(item.to);
}
};
@ -338,3 +334,5 @@ export default class Dropdown extends PureComponent {
}
}
export default withRouter(Dropdown);

View file

@ -2,14 +2,13 @@ import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import 'wicg-inert';
import { multiply } from 'color-blend';
import { createBrowserHistory } from 'history';
export default class ModalRoot extends PureComponent {
import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/react_router';
static contextTypes = {
router: PropTypes.object,
};
class ModalRoot extends PureComponent {
static propTypes = {
children: PropTypes.node,
@ -20,6 +19,7 @@ export default class ModalRoot extends PureComponent {
b: PropTypes.number,
}),
ignoreFocus: PropTypes.bool,
...WithOptionalRouterPropTypes,
};
activeElement = this.props.children ? document.activeElement : null;
@ -55,7 +55,7 @@ export default class ModalRoot extends PureComponent {
componentDidMount () {
window.addEventListener('keyup', this.handleKeyUp, false);
window.addEventListener('keydown', this.handleKeyDown, false);
this.history = this.context.router ? this.context.router.history : createBrowserHistory();
this.history = this.props.history || createBrowserHistory();
}
UNSAFE_componentWillReceiveProps (nextProps) {
@ -156,3 +156,5 @@ export default class ModalRoot extends PureComponent {
}
}
export default withOptionalRouter(ModalRoot);

View file

@ -1,35 +0,0 @@
import { PureComponent } from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import AccountNavigation from 'mastodon/features/account/navigation';
import Trends from 'mastodon/features/getting_started/containers/trends_container';
import { showTrends } from 'mastodon/initial_state';
const DefaultNavigation = () => (
showTrends ? (
<>
<div className='flex-spacer' />
<Trends />
</>
) : null
);
class NavigationPortal extends PureComponent {
render () {
return (
<Switch>
<Route path='/@:acct' exact component={AccountNavigation} />
<Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} />
<Route path='/@:acct/with_replies' exact component={AccountNavigation} />
<Route path='/@:acct/followers' exact component={AccountNavigation} />
<Route path='/@:acct/following' exact component={AccountNavigation} />
<Route path='/@:acct/media' exact component={AccountNavigation} />
<Route component={DefaultNavigation} />
</Switch>
);
}
}
export default withRouter(NavigationPortal);

View file

@ -0,0 +1,25 @@
import { Switch, Route } from 'react-router-dom';
import AccountNavigation from 'mastodon/features/account/navigation';
import Trends from 'mastodon/features/getting_started/containers/trends_container';
import { showTrends } from 'mastodon/initial_state';
const DefaultNavigation: React.FC = () =>
showTrends ? (
<>
<div className='flex-spacer' />
<Trends />
</>
) : null;
export const NavigationPortal: React.FC = () => (
<Switch>
<Route path='/@:acct' exact component={AccountNavigation} />
<Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} />
<Route path='/@:acct/with_replies' exact component={AccountNavigation} />
<Route path='/@:acct/followers' exact component={AccountNavigation} />
<Route path='/@:acct/following' exact component={AccountNavigation} />
<Route path='/@:acct/media' exact component={AccountNavigation} />
<Route component={DefaultNavigation} />
</Switch>
);

View file

@ -1,15 +1,18 @@
import type { PropsWithChildren } from 'react';
import React from 'react';
import { createBrowserHistory } from 'history';
import { Router as OriginalRouter } from 'react-router';
import type { LocationDescriptor, Path } from 'history';
import { createBrowserHistory } from 'history';
import { layoutFromWindow } from 'mastodon/is_mobile';
interface MastodonLocationState {
fromMastodon?: boolean;
mastodonModalKey?: string;
}
type HistoryPath = Path | LocationDescriptor<MastodonLocationState>;
const browserHistory = createBrowserHistory<
MastodonLocationState | undefined
@ -17,25 +20,36 @@ const browserHistory = createBrowserHistory<
const originalPush = browserHistory.push.bind(browserHistory);
const originalReplace = browserHistory.replace.bind(browserHistory);
browserHistory.push = (path: string, state?: MastodonLocationState) => {
function extractRealPath(path: HistoryPath) {
if (typeof path === 'string') return path;
else return path.pathname;
}
browserHistory.push = (path: HistoryPath, state?: MastodonLocationState) => {
state = state ?? {};
state.fromMastodon = true;
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
originalPush(`/deck${path}`, state);
const realPath = extractRealPath(path);
if (!realPath) return;
if (layoutFromWindow() === 'multi-column' && !realPath.startsWith('/deck')) {
originalPush(`/deck${realPath}`, state);
} else {
originalPush(path, state);
}
};
browserHistory.replace = (path: string, state?: MastodonLocationState) => {
browserHistory.replace = (path: HistoryPath, state?: MastodonLocationState) => {
if (browserHistory.location.state?.fromMastodon) {
state = state ?? {};
state.fromMastodon = true;
}
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
originalReplace(`/deck${path}`, state);
const realPath = extractRealPath(path);
if (!realPath) return;
if (layoutFromWindow() === 'multi-column' && !realPath.startsWith('/deck')) {
originalReplace(`/deck${realPath}`, state);
} else {
originalReplace(path, state);
}

View file

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import { Children, cloneElement, PureComponent } from 'react';
import classNames from 'classnames';
import { useLocation } from 'react-router-dom';
import { List as ImmutableList } from 'immutable';
import { connect } from 'react-redux';
@ -34,11 +35,32 @@ const mapStateToProps = (state, { scrollKey }) => {
};
};
class ScrollableList extends PureComponent {
// This component only exists to be able to call useLocation()
const IOArticleContainerWrapper = ({id, index, listLength, intersectionObserverWrapper, trackScroll, scrollKey, children}) => {
const location = useLocation();
static contextTypes = {
router: PropTypes.object,
};
return (<IntersectionObserverArticleContainer
id={id}
index={index}
listLength={listLength}
intersectionObserverWrapper={intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${location.key}:${scrollKey}` : null}
>
{children}
</IntersectionObserverArticleContainer>);
};
IOArticleContainerWrapper.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
scrollKey: PropTypes.string.isRequired,
intersectionObserverWrapper: PropTypes.object.isRequired,
trackScroll: PropTypes.bool.isRequired,
children: PropTypes.node,
};
class ScrollableList extends PureComponent {
static propTypes = {
scrollKey: PropTypes.string.isRequired,
@ -331,13 +353,14 @@ class ScrollableList extends PureComponent {
{loadPending}
{Children.map(this.props.children, (child, index) => (
<IntersectionObserverArticleContainer
<IOArticleContainerWrapper
key={child.key}
id={child.key}
index={index}
listLength={childrenCount}
intersectionObserverWrapper={this.intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
trackScroll={trackScroll}
scrollKey={scrollKey}
>
{cloneElement(child, {
getScrollPosition: this.getScrollPosition,
@ -345,7 +368,7 @@ class ScrollableList extends PureComponent {
cachedMediaWidth: this.state.cachedMediaWidth,
cacheMediaWidth: this.cacheMediaWidth,
})}
</IntersectionObserverArticleContainer>
</IOArticleContainerWrapper>
))}
{loadMore}

View file

@ -11,6 +11,7 @@ import { HotKeys } from 'react-hotkeys';
import { Icon } from 'mastodon/components/icon';
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
import { withOptionalRouter, WithRouterPropTypes } from 'mastodon/utils/react_router';
import Card from '../features/status/components/card';
// We use the component (and not the container) since we do not want
@ -72,10 +73,6 @@ const messages = defineMessages({
class Status extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
status: ImmutablePropTypes.map,
account: ImmutablePropTypes.map,
@ -116,6 +113,7 @@ class Status extends ImmutablePureComponent {
inUse: PropTypes.bool,
available: PropTypes.bool,
}),
...WithRouterPropTypes,
};
// Avoid checking props that are functions (and whose equality will always
@ -258,7 +256,7 @@ class Status extends ImmutablePureComponent {
handleHotkeyReply = e => {
e.preventDefault();
this.props.onReply(this._properStatus(), this.context.router.history);
this.props.onReply(this._properStatus(), this.props.history);
};
handleHotkeyFavourite = () => {
@ -271,7 +269,7 @@ class Status extends ImmutablePureComponent {
handleHotkeyMention = e => {
e.preventDefault();
this.props.onMention(this._properStatus().get('account'), this.context.router.history);
this.props.onMention(this._properStatus().get('account'), this.props.history);
};
handleHotkeyOpen = () => {
@ -280,14 +278,14 @@ class Status extends ImmutablePureComponent {
return;
}
const { router } = this.context;
const { history } = this.props;
const status = this._properStatus();
if (!router) {
if (!history) {
return;
}
router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
};
handleHotkeyOpenProfile = () => {
@ -295,14 +293,14 @@ class Status extends ImmutablePureComponent {
};
_openProfile = (proper = true) => {
const { router } = this.context;
const { history } = this.props;
const status = proper ? this._properStatus() : this.props.status;
if (!router) {
if (!history) {
return;
}
router.history.push(`/@${status.getIn(['account', 'acct'])}`);
history.push(`/@${status.getIn(['account', 'acct'])}`);
};
handleHotkeyMoveUp = e => {
@ -596,4 +594,4 @@ class Status extends ImmutablePureComponent {
}
export default injectIntl(Status);
export default withOptionalRouter(injectIntl(Status));

View file

@ -3,12 +3,14 @@ import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import DropdownMenuContainer from '../containers/dropdown_menu_container';
import { me } from '../initial_state';
@ -61,7 +63,6 @@ const mapStateToProps = (state, { status }) => ({
class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object,
};
@ -92,6 +93,7 @@ class StatusActionBar extends ImmutablePureComponent {
withCounters: PropTypes.bool,
scrollKey: PropTypes.string,
intl: PropTypes.object.isRequired,
...WithRouterPropTypes,
};
// Avoid checking props that are functions (and whose equality will always
@ -106,7 +108,7 @@ class StatusActionBar extends ImmutablePureComponent {
const { signedIn } = this.context.identity;
if (signedIn) {
this.props.onReply(this.props.status, this.context.router.history);
this.props.onReply(this.props.status, this.props.history);
} else {
this.props.onInteractionModal('reply', this.props.status);
}
@ -145,15 +147,15 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history);
this.props.onDelete(this.props.status, this.props.history);
};
handleRedraftClick = () => {
this.props.onDelete(this.props.status, this.context.router.history, true);
this.props.onDelete(this.props.status, this.props.history, true);
};
handleEditClick = () => {
this.props.onEdit(this.props.status, this.context.router.history);
this.props.onEdit(this.props.status, this.props.history);
};
handlePinClick = () => {
@ -161,11 +163,11 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
this.props.onMention(this.props.status.get('account'), this.props.history);
};
handleDirectClick = () => {
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
this.props.onDirect(this.props.status.get('account'), this.props.history);
};
handleMuteClick = () => {
@ -205,7 +207,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleOpen = () => {
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
};
handleEmbed = () => {
@ -386,4 +388,4 @@ class StatusActionBar extends ImmutablePureComponent {
}
export default connect(mapStateToProps)(injectIntl(StatusActionBar));
export default withRouter(connect(mapStateToProps)(injectIntl(StatusActionBar)));

View file

@ -4,7 +4,7 @@ import { PureComponent } from 'react';
import { FormattedMessage, injectIntl } from 'react-intl';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { Link, withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
@ -68,7 +68,6 @@ const mapStateToProps = state => ({
class StatusContent extends PureComponent {
static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object,
};
@ -83,6 +82,10 @@ class StatusContent extends PureComponent {
onCollapsedToggle: PropTypes.func,
languages: ImmutablePropTypes.map,
intl: PropTypes.object,
// from react-router
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
state = {
@ -173,18 +176,18 @@ class StatusContent extends PureComponent {
}
onMentionClick = (mention, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/@${mention.get('acct')}`);
this.props.history.push(`/@${mention.get('acct')}`);
}
};
onHashtagClick = (hashtag, e) => {
hashtag = hashtag.replace(/^#/, '');
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/tags/${hashtag}`);
this.props.history.push(`/tags/${hashtag}`);
}
};
@ -247,7 +250,7 @@ class StatusContent extends PureComponent {
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
const language = status.getIn(['translation', 'language']) || status.get('language');
const classNames = classnames('status__content', {
'status__content--with-action': this.props.onClick && this.context.router,
'status__content--with-action': this.props.onClick && this.props.history,
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
'status__content--collapsed': renderReadMore,
});
@ -324,4 +327,4 @@ class StatusContent extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(StatusContent));
export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent)));