From 986397b3a255e5078ff4830a7d4d4687a12f4c19 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 13 Jul 2021 15:45:17 +0200 Subject: [PATCH] Improve modal flow and back button handling (#16499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor shouldUpdateScroll passing So far, shouldUpdateScroll has been manually passed down from the very top of the React component hierarchy even though it is a static function common to all ScrollContainer instances, so replaced that with a custom class extending ScrollContainer. * Generalize “press back to close modal” to any modal and to public pages * Fix boost confirmation modal closing media modal --- .../mastodon/components/modal_root.js | 36 ++++++++++++ .../mastodon/components/scrollable_list.js | 7 +-- .../mastodon/components/status_list.js | 5 +- .../mastodon/containers/mastodon.js | 6 +- .../mastodon/containers/scroll_container.js | 18 ++++++ .../features/account_gallery/index.js | 7 +-- .../features/account_timeline/index.js | 4 +- .../mastodon/features/blocks/index.js | 4 +- .../features/bookmarked_statuses/index.js | 4 +- .../features/community_timeline/index.js | 4 +- .../components/conversations_list.js | 1 - .../features/direct_timeline/index.js | 4 +- .../mastodon/features/directory/index.js | 7 +-- .../mastodon/features/domain_blocks/index.js | 4 +- .../features/favourited_statuses/index.js | 4 +- .../mastodon/features/favourites/index.js | 4 +- .../features/follow_requests/index.js | 4 +- .../mastodon/features/followers/index.js | 4 +- .../mastodon/features/following/index.js | 4 +- .../features/hashtag_timeline/index.js | 4 +- .../mastodon/features/home_timeline/index.js | 4 +- .../mastodon/features/list_timeline/index.js | 4 +- .../mastodon/features/lists/index.js | 3 +- .../mastodon/features/mutes/index.js | 4 +- .../mastodon/features/notifications/index.js | 4 +- .../features/pinned_statuses/index.js | 4 +- .../features/public_timeline/index.js | 4 +- .../mastodon/features/reblogs/index.js | 4 +- .../mastodon/features/status/index.js | 6 +- .../features/ui/components/audio_modal.js | 27 --------- .../features/ui/components/media_modal.js | 24 -------- .../features/ui/components/video_modal.js | 24 -------- .../features/ui/containers/modal_container.js | 4 +- app/javascript/mastodon/features/ui/index.js | 58 +++++++++---------- app/javascript/mastodon/reducers/modal.js | 14 ++--- 35 files changed, 123 insertions(+), 200 deletions(-) create mode 100644 app/javascript/mastodon/containers/scroll_container.js diff --git a/app/javascript/mastodon/components/modal_root.js b/app/javascript/mastodon/components/modal_root.js index 26344528e..8c9409d01 100644 --- a/app/javascript/mastodon/components/modal_root.js +++ b/app/javascript/mastodon/components/modal_root.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import 'wicg-inert'; +import { createBrowserHistory } from 'history'; import { multiply } from 'color-blend'; export default class ModalRoot extends React.PureComponent { @@ -48,6 +49,7 @@ export default class ModalRoot extends React.PureComponent { componentDidMount () { window.addEventListener('keyup', this.handleKeyUp, false); window.addEventListener('keydown', this.handleKeyDown, false); + this.history = this.context.router ? this.context.router.history : createBrowserHistory(); } componentWillReceiveProps (nextProps) { @@ -69,6 +71,14 @@ export default class ModalRoot extends React.PureComponent { this.activeElement.focus({ preventScroll: true }); this.activeElement = null; }).catch(console.error); + + this._handleModalClose(); + } + if (this.props.children && !prevProps.children) { + this._handleModalOpen(); + } + if (this.props.children) { + this._ensureHistoryBuffer(); } } @@ -77,6 +87,32 @@ export default class ModalRoot extends React.PureComponent { window.removeEventListener('keydown', this.handleKeyDown); } + _handleModalOpen () { + this._modalHistoryKey = Date.now(); + this.unlistenHistory = this.history.listen((_, action) => { + if (action === 'POP') { + this.props.onClose(); + } + }); + } + + _handleModalClose () { + if (this.unlistenHistory) { + this.unlistenHistory(); + } + const { state } = this.history.location; + if (state && state.mastodonModalKey === this._modalHistoryKey) { + this.history.goBack(); + } + } + + _ensureHistoryBuffer () { + const { pathname, state } = this.history.location; + if (!state || state.mastodonModalKey !== this._modalHistoryKey) { + this.history.push(pathname, { ...state, mastodonModalKey: this._modalHistoryKey }); + } + } + getSiblings = () => { return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node); } diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index 2689b18ef..68a178512 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { ScrollContainer } from 'react-router-scroll-4'; +import ScrollContainer from 'mastodon/containers/scroll_container'; import PropTypes from 'prop-types'; import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container'; import LoadMore from './load_more'; @@ -34,7 +34,6 @@ class ScrollableList extends PureComponent { onScrollToTop: PropTypes.func, onScroll: PropTypes.func, trackScroll: PropTypes.bool, - shouldUpdateScroll: PropTypes.func, isLoading: PropTypes.bool, showLoading: PropTypes.bool, hasMore: PropTypes.bool, @@ -290,7 +289,7 @@ class ScrollableList extends PureComponent { } render () { - const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, emptyMessage, onLoadMore } = this.props; + const { children, scrollKey, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, emptyMessage, onLoadMore } = this.props; const { fullscreen } = this.state; const childrenCount = React.Children.count(children); @@ -356,7 +355,7 @@ class ScrollableList extends PureComponent { if (trackScroll) { return ( - + {scrollableArea} ); diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 25411c127..eaaffcc3a 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -18,7 +18,6 @@ export default class StatusList extends ImmutablePureComponent { onScrollToTop: PropTypes.func, onScroll: PropTypes.func, trackScroll: PropTypes.bool, - shouldUpdateScroll: PropTypes.func, isLoading: PropTypes.bool, isPartial: PropTypes.bool, hasMore: PropTypes.bool, @@ -77,7 +76,7 @@ export default class StatusList extends ImmutablePureComponent { } render () { - const { statusIds, featuredStatusIds, shouldUpdateScroll, onLoadMore, timelineId, ...other } = this.props; + const { statusIds, featuredStatusIds, onLoadMore, timelineId, ...other } = this.props; const { isLoading, isPartial } = other; if (isPartial) { @@ -120,7 +119,7 @@ export default class StatusList extends ImmutablePureComponent { } return ( - + {scrollableContent} ); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 513b59908..892ff1ca9 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -10,8 +10,6 @@ import { hydrateStore } from '../actions/store'; import { connectUserStream } from '../actions/streaming'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; -import { previewState as previewMediaState } from 'mastodon/features/ui/components/media_modal'; -import { previewState as previewVideoState } from 'mastodon/features/ui/components/video_modal'; import initialState from '../initial_state'; import ErrorBoundary from '../components/error_boundary'; @@ -41,8 +39,8 @@ export default class Mastodon extends React.PureComponent { } } - shouldUpdateScroll (_, { location }) { - return location.state !== previewMediaState && location.state !== previewVideoState; + shouldUpdateScroll (prevRouterProps, { location }) { + return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey); } render () { diff --git a/app/javascript/mastodon/containers/scroll_container.js b/app/javascript/mastodon/containers/scroll_container.js new file mode 100644 index 000000000..d21ff6368 --- /dev/null +++ b/app/javascript/mastodon/containers/scroll_container.js @@ -0,0 +1,18 @@ +import { ScrollContainer as OriginalScrollContainer } from 'react-router-scroll-4'; + +// ScrollContainer is used to automatically scroll to the top when pushing a +// new history state and remembering the scroll position when going back. +// There are a few things we need to do differently, though. +const defaultShouldUpdateScroll = (prevRouterProps, { location }) => { + // If the change is caused by opening a modal, do not scroll to top + return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey); +}; + +export default +class ScrollContainer extends OriginalScrollContainer { + + static defaultProps = { + shouldUpdateScroll: defaultShouldUpdateScroll, + }; + +} diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 015a6a6d7..e199c929e 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -11,7 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { getAccountGallery } from 'mastodon/selectors'; import MediaItem from './components/media_item'; import HeaderContainer from '../account_timeline/containers/header_container'; -import { ScrollContainer } from 'react-router-scroll-4'; +import ScrollContainer from 'mastodon/containers/scroll_container'; import LoadMore from 'mastodon/components/load_more'; import MissingIndicator from 'mastodon/components/missing_indicator'; import { openModal } from 'mastodon/actions/modal'; @@ -29,7 +29,6 @@ const mapStateToProps = (state, props) => ({ class LoadMoreMedia extends ImmutablePureComponent { static propTypes = { - shouldUpdateScroll: PropTypes.func, maxId: PropTypes.string, onLoadMore: PropTypes.func.isRequired, }; @@ -127,7 +126,7 @@ class AccountGallery extends ImmutablePureComponent { } render () { - const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props; + const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props; const { width } = this.state; if (!isAccount) { @@ -164,7 +163,7 @@ class AccountGallery extends ImmutablePureComponent { - +
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index fa4239d6f..821c852b6 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -50,7 +50,6 @@ class AccountTimeline extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, statusIds: ImmutablePropTypes.list, featuredStatusIds: ImmutablePropTypes.list, isLoading: PropTypes.bool, @@ -115,7 +114,7 @@ class AccountTimeline extends ImmutablePureComponent { } render () { - const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl } = this.props; + const { statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl } = this.props; if (!isAccount) { return ( @@ -162,7 +161,6 @@ class AccountTimeline extends ImmutablePureComponent { isLoading={isLoading} hasMore={hasMore} onLoadMore={this.handleLoadMore} - shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} bindToDocument={!multiColumn} timelineId='account' diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js index 107deb841..7ec177434 100644 --- a/app/javascript/mastodon/features/blocks/index.js +++ b/app/javascript/mastodon/features/blocks/index.js @@ -29,7 +29,6 @@ class Blocks extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, isLoading: PropTypes.bool, @@ -46,7 +45,7 @@ class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, accountIds, shouldUpdateScroll, hasMore, multiColumn, isLoading } = this.props; + const { intl, accountIds, hasMore, multiColumn, isLoading } = this.props; if (!accountIds) { return ( @@ -66,7 +65,6 @@ class Blocks extends ImmutablePureComponent { onLoadMore={this.handleLoadMore} hasMore={hasMore} isLoading={isLoading} - shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} bindToDocument={!multiColumn} > diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.js b/app/javascript/mastodon/features/bookmarked_statuses/index.js index c37cb9176..cf067d954 100644 --- a/app/javascript/mastodon/features/bookmarked_statuses/index.js +++ b/app/javascript/mastodon/features/bookmarked_statuses/index.js @@ -27,7 +27,6 @@ class Bookmarks extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, statusIds: ImmutablePropTypes.list.isRequired, intl: PropTypes.object.isRequired, columnId: PropTypes.string, @@ -68,7 +67,7 @@ class Bookmarks extends ImmutablePureComponent { }, 300, { leading: true }) render () { - const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; + const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const pinned = !!columnId; const emptyMessage = ; @@ -93,7 +92,6 @@ class Bookmarks extends ImmutablePureComponent { hasMore={hasMore} isLoading={isLoading} onLoadMore={this.handleLoadMore} - shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index b3cd39685..30f776048 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -41,7 +41,6 @@ class CommunityTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, columnId: PropTypes.string, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, @@ -103,7 +102,7 @@ class CommunityTimeline extends React.PureComponent { } render () { - const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, onlyMedia } = this.props; + const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; return ( @@ -127,7 +126,6 @@ class CommunityTimeline extends React.PureComponent { timelineId={`community${onlyMedia ? ':media' : ''}`} onLoadMore={this.handleLoadMore} emptyMessage={} - shouldUpdateScroll={shouldUpdateScroll} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js index 4ee8e5212..fd1df7256 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js +++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js @@ -14,7 +14,6 @@ export default class ConversationsList extends ImmutablePureComponent { hasMore: PropTypes.bool, isLoading: PropTypes.bool, onLoadMore: PropTypes.func, - shouldUpdateScroll: PropTypes.func, }; getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id) diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js index 5ce795760..68523666c 100644 --- a/app/javascript/mastodon/features/direct_timeline/index.js +++ b/app/javascript/mastodon/features/direct_timeline/index.js @@ -19,7 +19,6 @@ class DirectTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, columnId: PropTypes.string, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, @@ -71,7 +70,7 @@ class DirectTimeline extends React.PureComponent { } render () { - const { intl, hasUnread, columnId, multiColumn, shouldUpdateScroll } = this.props; + const { intl, hasUnread, columnId, multiColumn } = this.props; const pinned = !!columnId; return ( @@ -93,7 +92,6 @@ class DirectTimeline extends React.PureComponent { timelineId='direct' onLoadMore={this.handleLoadMore} emptyMessage={} - shouldUpdateScroll={shouldUpdateScroll} /> ); diff --git a/app/javascript/mastodon/features/directory/index.js b/app/javascript/mastodon/features/directory/index.js index 2f91e759b..88f20d330 100644 --- a/app/javascript/mastodon/features/directory/index.js +++ b/app/javascript/mastodon/features/directory/index.js @@ -12,7 +12,7 @@ import AccountCard from './components/account_card'; import RadioButton from 'mastodon/components/radio_button'; import classNames from 'classnames'; import LoadMore from 'mastodon/components/load_more'; -import { ScrollContainer } from 'react-router-scroll-4'; +import ScrollContainer from 'mastodon/containers/scroll_container'; const messages = defineMessages({ title: { id: 'column.directory', defaultMessage: 'Browse profiles' }, @@ -40,7 +40,6 @@ class Directory extends React.PureComponent { isLoading: PropTypes.bool, accountIds: ImmutablePropTypes.list.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, columnId: PropTypes.string, intl: PropTypes.object.isRequired, multiColumn: PropTypes.bool, @@ -125,7 +124,7 @@ class Directory extends React.PureComponent { } render () { - const { isLoading, accountIds, intl, columnId, multiColumn, domain, shouldUpdateScroll } = this.props; + const { isLoading, accountIds, intl, columnId, multiColumn, domain } = this.props; const { order, local } = this.getParams(this.props, this.state); const pinned = !!columnId; @@ -163,7 +162,7 @@ class Directory extends React.PureComponent { multiColumn={multiColumn} /> - {multiColumn && !pinned ? {scrollableArea} : scrollableArea} + {multiColumn && !pinned ? {scrollableArea} : scrollableArea} ); } diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js index a6d988912..edb80aef4 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.js +++ b/app/javascript/mastodon/features/domain_blocks/index.js @@ -29,7 +29,6 @@ class Blocks extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, hasMore: PropTypes.bool, domains: ImmutablePropTypes.orderedSet, intl: PropTypes.object.isRequired, @@ -45,7 +44,7 @@ class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, domains, shouldUpdateScroll, hasMore, multiColumn } = this.props; + const { intl, domains, hasMore, multiColumn } = this.props; if (!domains) { return ( @@ -64,7 +63,6 @@ class Blocks extends ImmutablePureComponent { scrollKey='domain_blocks' onLoadMore={this.handleLoadMore} hasMore={hasMore} - shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} bindToDocument={!multiColumn} > diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js index db8a3f815..9606a144c 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.js @@ -27,7 +27,6 @@ class Favourites extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, statusIds: ImmutablePropTypes.list.isRequired, intl: PropTypes.object.isRequired, columnId: PropTypes.string, @@ -68,7 +67,7 @@ class Favourites extends ImmutablePureComponent { }, 300, { leading: true }) render () { - const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; + const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const pinned = !!columnId; const emptyMessage = ; @@ -93,7 +92,6 @@ class Favourites extends ImmutablePureComponent { hasMore={hasMore} isLoading={isLoading} onLoadMore={this.handleLoadMore} - shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js index 75cb00c0e..ac94ae18a 100644 --- a/app/javascript/mastodon/features/favourites/index.js +++ b/app/javascript/mastodon/features/favourites/index.js @@ -27,7 +27,6 @@ class Favourites extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, multiColumn: PropTypes.bool, intl: PropTypes.object.isRequired, @@ -50,7 +49,7 @@ class Favourites extends ImmutablePureComponent { } render () { - const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props; + const { intl, accountIds, multiColumn } = this.props; if (!accountIds) { return ( @@ -74,7 +73,6 @@ class Favourites extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js index 18df9d25c..1f9b635bb 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.js @@ -32,7 +32,6 @@ class FollowRequests extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, hasMore: PropTypes.bool, isLoading: PropTypes.bool, accountIds: ImmutablePropTypes.list, @@ -51,7 +50,7 @@ class FollowRequests extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, shouldUpdateScroll, accountIds, hasMore, multiColumn, locked, domain, isLoading } = this.props; + const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading } = this.props; if (!accountIds) { return ( @@ -80,7 +79,6 @@ class FollowRequests extends ImmutablePureComponent { onLoadMore={this.handleLoadMore} hasMore={hasMore} isLoading={isLoading} - shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} bindToDocument={!multiColumn} prepend={unlockedPrependMessage} diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js index ae00d13d3..ee747f0da 100644 --- a/app/javascript/mastodon/features/followers/index.js +++ b/app/javascript/mastodon/features/followers/index.js @@ -43,7 +43,6 @@ class Followers extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, isLoading: PropTypes.bool, @@ -73,7 +72,7 @@ class Followers extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props; + const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props; if (!isAccount) { return ( @@ -112,7 +111,6 @@ class Followers extends ImmutablePureComponent { hasMore={hasMore} isLoading={isLoading} onLoadMore={this.handleLoadMore} - shouldUpdateScroll={shouldUpdateScroll} prepend={} alwaysPrepend append={remoteMessage} diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js index 666ec7a7f..804df803e 100644 --- a/app/javascript/mastodon/features/following/index.js +++ b/app/javascript/mastodon/features/following/index.js @@ -43,7 +43,6 @@ class Following extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, isLoading: PropTypes.bool, @@ -73,7 +72,7 @@ class Following extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props; + const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props; if (!isAccount) { return ( @@ -112,7 +111,6 @@ class Following extends ImmutablePureComponent { hasMore={hasMore} isLoading={isLoading} onLoadMore={this.handleLoadMore} - shouldUpdateScroll={shouldUpdateScroll} prepend={} alwaysPrepend append={remoteMessage} diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index 5ccd9f8ea..6a808eb30 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -24,7 +24,6 @@ class HashtagTimeline extends React.PureComponent { params: PropTypes.object.isRequired, columnId: PropTypes.string, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, }; @@ -130,7 +129,7 @@ class HashtagTimeline extends React.PureComponent { } render () { - const { shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props; + const { hasUnread, columnId, multiColumn } = this.props; const { id, local } = this.props.params; const pinned = !!columnId; @@ -156,7 +155,6 @@ class HashtagTimeline extends React.PureComponent { timelineId={`hashtag:${id}${local ? ':local' : ''}`} onLoadMore={this.handleLoadMore} emptyMessage={} - shouldUpdateScroll={shouldUpdateScroll} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index b85c69af7..dc440f2fe 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -34,7 +34,6 @@ class HomeTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, isPartial: PropTypes.bool, @@ -112,7 +111,7 @@ class HomeTimeline extends React.PureComponent { } render () { - const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; + const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const pinned = !!columnId; let announcementsButton = null; @@ -154,7 +153,6 @@ class HomeTimeline extends React.PureComponent { onLoadMore={this.handleLoadMore} timelineId='home' emptyMessage={ }} />} - shouldUpdateScroll={shouldUpdateScroll} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js index 8eb645630..8010274f8 100644 --- a/app/javascript/mastodon/features/list_timeline/index.js +++ b/app/javascript/mastodon/features/list_timeline/index.js @@ -41,7 +41,6 @@ class ListTimeline extends React.PureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, columnId: PropTypes.string, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, @@ -142,7 +141,7 @@ class ListTimeline extends React.PureComponent { } render () { - const { shouldUpdateScroll, hasUnread, columnId, multiColumn, list, intl } = this.props; + const { hasUnread, columnId, multiColumn, list, intl } = this.props; const { id } = this.props.params; const pinned = !!columnId; const title = list ? list.get('title') : id; @@ -207,7 +206,6 @@ class ListTimeline extends React.PureComponent { timelineId={`list:${id}`} onLoadMore={this.handleLoadMore} emptyMessage={} - shouldUpdateScroll={shouldUpdateScroll} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/lists/index.js b/app/javascript/mastodon/features/lists/index.js index ca1fa1f5e..29c311809 100644 --- a/app/javascript/mastodon/features/lists/index.js +++ b/app/javascript/mastodon/features/lists/index.js @@ -48,7 +48,7 @@ class Lists extends ImmutablePureComponent { } render () { - const { intl, shouldUpdateScroll, lists, multiColumn } = this.props; + const { intl, lists, multiColumn } = this.props; if (!lists) { return ( @@ -68,7 +68,6 @@ class Lists extends ImmutablePureComponent { } bindToDocument={!multiColumn} diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js index 17ff5c762..c1d50d194 100644 --- a/app/javascript/mastodon/features/mutes/index.js +++ b/app/javascript/mastodon/features/mutes/index.js @@ -29,7 +29,6 @@ class Mutes extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, hasMore: PropTypes.bool, isLoading: PropTypes.bool, accountIds: ImmutablePropTypes.list, @@ -46,7 +45,7 @@ class Mutes extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, shouldUpdateScroll, hasMore, accountIds, multiColumn, isLoading } = this.props; + const { intl, hasMore, accountIds, multiColumn, isLoading } = this.props; if (!accountIds) { return ( @@ -66,7 +65,6 @@ class Mutes extends ImmutablePureComponent { onLoadMore={this.handleLoadMore} hasMore={hasMore} isLoading={isLoading} - shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} bindToDocument={!multiColumn} > diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index 1a621eca9..a6a277d7e 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -74,7 +74,6 @@ class Notifications extends React.PureComponent { notifications: ImmutablePropTypes.list.isRequired, showFilterBar: PropTypes.bool.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, intl: PropTypes.object.isRequired, isLoading: PropTypes.bool, isUnread: PropTypes.bool, @@ -176,7 +175,7 @@ class Notifications extends React.PureComponent { }; render () { - const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props; + const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props; const pinned = !!columnId; const emptyMessage = ; @@ -227,7 +226,6 @@ class Notifications extends React.PureComponent { onLoadPending={this.handleLoadPending} onScrollToTop={this.handleScrollToTop} onScroll={this.handleScroll} - shouldUpdateScroll={shouldUpdateScroll} bindToDocument={!multiColumn} > {scrollableContent} diff --git a/app/javascript/mastodon/features/pinned_statuses/index.js b/app/javascript/mastodon/features/pinned_statuses/index.js index ad5c9cafc..f32bd6d23 100644 --- a/app/javascript/mastodon/features/pinned_statuses/index.js +++ b/app/javascript/mastodon/features/pinned_statuses/index.js @@ -24,7 +24,6 @@ class PinnedStatuses extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, statusIds: ImmutablePropTypes.list.isRequired, intl: PropTypes.object.isRequired, hasMore: PropTypes.bool.isRequired, @@ -44,7 +43,7 @@ class PinnedStatuses extends ImmutablePureComponent { } render () { - const { intl, shouldUpdateScroll, statusIds, hasMore, multiColumn } = this.props; + const { intl, statusIds, hasMore, multiColumn } = this.props; return ( @@ -53,7 +52,6 @@ class PinnedStatuses extends ImmutablePureComponent { statusIds={statusIds} scrollKey='pinned_statuses' hasMore={hasMore} - shouldUpdateScroll={shouldUpdateScroll} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index 988b1b070..b1d5518af 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -43,7 +43,6 @@ class PublicTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, intl: PropTypes.object.isRequired, columnId: PropTypes.string, multiColumn: PropTypes.bool, @@ -106,7 +105,7 @@ class PublicTimeline extends React.PureComponent { } render () { - const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props; + const { intl, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props; const pinned = !!columnId; return ( @@ -130,7 +129,6 @@ class PublicTimeline extends React.PureComponent { trackScroll={!pinned} scrollKey={`public_timeline-${columnId}`} emptyMessage={} - shouldUpdateScroll={shouldUpdateScroll} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js index 4becb5fb7..0fbd09415 100644 --- a/app/javascript/mastodon/features/reblogs/index.js +++ b/app/javascript/mastodon/features/reblogs/index.js @@ -27,7 +27,6 @@ class Reblogs extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, multiColumn: PropTypes.bool, intl: PropTypes.object.isRequired, @@ -50,7 +49,7 @@ class Reblogs extends ImmutablePureComponent { } render () { - const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props; + const { intl, accountIds, multiColumn } = this.props; if (!accountIds) { return ( @@ -74,7 +73,6 @@ class Reblogs extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index df8362a1b..69cd245fb 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -45,7 +45,7 @@ import { initBlockModal } from '../../actions/blocks'; import { initBoostModal } from '../../actions/boosts'; import { initReport } from '../../actions/reports'; import { makeGetStatus, makeGetPictureInPicture } from '../../selectors'; -import { ScrollContainer } from 'react-router-scroll-4'; +import ScrollContainer from 'mastodon/containers/scroll_container'; import ColumnBackButton from '../../components/column_back_button'; import ColumnHeader from '../../components/column_header'; import StatusContainer from '../../containers/status_container'; @@ -498,7 +498,7 @@ class Status extends ImmutablePureComponent { render () { let ancestors, descendants; - const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props; + const { status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props; const { fullscreen } = this.state; if (status === null) { @@ -541,7 +541,7 @@ class Status extends ImmutablePureComponent { )} /> - +
{ancestors} diff --git a/app/javascript/mastodon/features/ui/components/audio_modal.js b/app/javascript/mastodon/features/ui/components/audio_modal.js index 0676bd9cf..c46fefce8 100644 --- a/app/javascript/mastodon/features/ui/components/audio_modal.js +++ b/app/javascript/mastodon/features/ui/components/audio_modal.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import Audio from 'mastodon/features/audio'; import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { previewState } from './video_modal'; import Footer from 'mastodon/features/picture_in_picture/components/footer'; const mapStateToProps = (state, { statusId }) => ({ @@ -25,32 +24,6 @@ class AudioModal extends ImmutablePureComponent { onChangeBackgroundColor: PropTypes.func.isRequired, }; - static contextTypes = { - router: PropTypes.object, - }; - - componentDidMount () { - if (this.context.router) { - const history = this.context.router.history; - - history.push(history.location.pathname, previewState); - - this.unlistenHistory = history.listen(() => { - this.props.onClose(); - }); - } - } - - componentWillUnmount () { - if (this.context.router) { - this.unlistenHistory(); - - if (this.context.router.history.location.state === previewState) { - this.context.router.history.goBack(); - } - } - } - render () { const { media, accountStaticAvatar, statusId, onClose } = this.props; const options = this.props.options || {}; diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index 08da10330..061776e24 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -20,8 +20,6 @@ const messages = defineMessages({ next: { id: 'lightbox.next', defaultMessage: 'Next' }, }); -export const previewState = 'previewMediaModal'; - export default @injectIntl class MediaModal extends ImmutablePureComponent { @@ -37,10 +35,6 @@ class MediaModal extends ImmutablePureComponent { volume: PropTypes.number, }; - static contextTypes = { - router: PropTypes.object, - }; - state = { index: null, navigationHidden: false, @@ -98,16 +92,6 @@ class MediaModal extends ImmutablePureComponent { componentDidMount () { window.addEventListener('keydown', this.handleKeyDown, false); - if (this.context.router) { - const history = this.context.router.history; - - history.push(history.location.pathname, previewState); - - this.unlistenHistory = history.listen(() => { - this.props.onClose(); - }); - } - this._sendBackgroundColor(); } @@ -131,14 +115,6 @@ class MediaModal extends ImmutablePureComponent { componentWillUnmount () { window.removeEventListener('keydown', this.handleKeyDown); - if (this.context.router) { - this.unlistenHistory(); - - if (this.context.router.history.location.state === previewState) { - this.context.router.history.goBack(); - } - } - this.props.onChangeBackgroundColor(null); } diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js index 2f13a175a..7e8e1329d 100644 --- a/app/javascript/mastodon/features/ui/components/video_modal.js +++ b/app/javascript/mastodon/features/ui/components/video_modal.js @@ -6,8 +6,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import Footer from 'mastodon/features/picture_in_picture/components/footer'; import { getAverageFromBlurhash } from 'mastodon/blurhash'; -export const previewState = 'previewVideoModal'; - export default class VideoModal extends ImmutablePureComponent { static propTypes = { @@ -22,19 +20,9 @@ export default class VideoModal extends ImmutablePureComponent { onChangeBackgroundColor: PropTypes.func.isRequired, }; - static contextTypes = { - router: PropTypes.object, - }; - componentDidMount () { - const { router } = this.context; const { media, onChangeBackgroundColor, onClose } = this.props; - if (router) { - router.history.push(router.history.location.pathname, previewState); - this.unlistenHistory = router.history.listen(() => onClose()); - } - const backgroundColor = getAverageFromBlurhash(media.get('blurhash')); if (backgroundColor) { @@ -42,18 +30,6 @@ export default class VideoModal extends ImmutablePureComponent { } } - componentWillUnmount () { - const { router } = this.context; - - if (router) { - this.unlistenHistory(); - - if (router.history.location.state === previewState) { - router.history.goBack(); - } - } - } - render () { const { media, statusId, onClose } = this.props; const options = this.props.options || {}; diff --git a/app/javascript/mastodon/features/ui/containers/modal_container.js b/app/javascript/mastodon/features/ui/containers/modal_container.js index 2d27180f7..ad1e8a2ee 100644 --- a/app/javascript/mastodon/features/ui/containers/modal_container.js +++ b/app/javascript/mastodon/features/ui/containers/modal_container.js @@ -3,8 +3,8 @@ import { closeModal } from '../../../actions/modal'; import ModalRoot from '../components/modal_root'; const mapStateToProps = state => ({ - type: state.get('modal').modalType, - props: state.get('modal').modalProps, + type: state.getIn(['modal', 0, 'modalType'], null), + props: state.getIn(['modal', 0, 'modalProps'], {}), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index c1c6ac739..756a69293 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -54,8 +54,6 @@ import { FollowRecommendations, } from './util/async-components'; import { me } from '../../initial_state'; -import { previewState as previewMediaState } from './components/media_modal'; -import { previewState as previewVideoState } from './components/video_modal'; import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; // Dummy import, to make sure that ends up in the application bundle. @@ -138,10 +136,6 @@ class SwitchingColumnsArea extends React.PureComponent { } } - shouldUpdateScroll (_, { location }) { - return location.state !== previewMediaState && location.state !== previewVideoState; - } - setRef = c => { if (c) { this.node = c.getWrappedInstance(); @@ -158,38 +152,38 @@ class SwitchingColumnsArea extends React.PureComponent { {redirect} - - - - - - - - - + + + + + + + + + - + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/app/javascript/mastodon/reducers/modal.js b/app/javascript/mastodon/reducers/modal.js index cb53887c7..ea81b4332 100644 --- a/app/javascript/mastodon/reducers/modal.js +++ b/app/javascript/mastodon/reducers/modal.js @@ -1,19 +1,15 @@ import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal'; import { TIMELINE_DELETE } from '../actions/timelines'; +import { Stack as ImmutableStack, Map as ImmutableMap } from 'immutable'; -const initialState = { - modalType: null, - modalProps: {}, -}; - -export default function modal(state = initialState, action) { +export default function modal(state = ImmutableStack(), action) { switch(action.type) { case MODAL_OPEN: - return { modalType: action.modalType, modalProps: action.modalProps }; + return state.unshift(ImmutableMap({ modalType: action.modalType, modalProps: action.modalProps })); case MODAL_CLOSE: - return (action.modalType === undefined || action.modalType === state.modalType) ? initialState : state; + return (action.modalType === undefined || action.modalType === state.getIn([0, 'modalType'])) ? state.shift() : state; case TIMELINE_DELETE: - return (state.modalProps.statusId === action.id) ? initialState : state; + return state.filterNot((modal) => modal.get('modalProps').statusId === action.id); default: return state; }