Merge pull request #1566 from ClearlyClaire/glitch-soc/feature/modal-stack

Fix boost/fav confirmation modals closing media modal
This commit is contained in:
Claire 2021-07-13 15:45:21 +02:00 committed by GitHub
commit 82bc8e7647
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 63 additions and 63 deletions

View file

@ -76,10 +76,13 @@ export default class ModalRoot extends React.PureComponent {
this.activeElement = null; this.activeElement = null;
}).catch(console.error); }).catch(console.error);
this.handleModalClose(); this._handleModalClose();
} }
if (this.props.children && !prevProps.children) { if (this.props.children && !prevProps.children) {
this.handleModalOpen(); this._handleModalOpen();
}
if (this.props.children) {
this._ensureHistoryBuffer();
} }
} }
@ -88,22 +91,29 @@ export default class ModalRoot extends React.PureComponent {
window.removeEventListener('keydown', this.handleKeyDown); window.removeEventListener('keydown', this.handleKeyDown);
} }
handleModalClose () { _handleModalOpen () {
this._modalHistoryKey = Date.now();
this.unlistenHistory = this.history.listen((_, action) => {
if (action === 'POP') {
this.props.onClose();
}
});
}
_handleModalClose () {
this.unlistenHistory(); this.unlistenHistory();
const state = this.history.location.state; const { state } = this.history.location;
if (state && state.mastodonModalOpen) { if (state && state.mastodonModalKey === this._modalHistoryKey) {
this.history.goBack(); this.history.goBack();
} }
} }
handleModalOpen () { _ensureHistoryBuffer () {
const history = this.history; const { pathname, state } = this.history.location;
const state = {...history.location.state, mastodonModalOpen: true}; if (!state || state.mastodonModalKey !== this._modalHistoryKey) {
history.push(history.location.pathname, state); this.history.push(pathname, { ...state, mastodonModalKey: this._modalHistoryKey });
this.unlistenHistory = history.listen(() => { }
this.props.onClose();
});
} }
getSiblings = () => { getSiblings = () => {

View file

@ -1,5 +1,5 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { ScrollContainer } from 'react-router-scroll-4'; import ScrollContainer from 'flavours/glitch/containers/scroll_container';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import IntersectionObserverArticleContainer from 'flavours/glitch/containers/intersection_observer_article_container'; import IntersectionObserverArticleContainer from 'flavours/glitch/containers/intersection_observer_article_container';
import LoadMore from './load_more'; import LoadMore from './load_more';
@ -34,7 +34,6 @@ class ScrollableList extends PureComponent {
onScrollToTop: PropTypes.func, onScrollToTop: PropTypes.func,
onScroll: PropTypes.func, onScroll: PropTypes.func,
trackScroll: PropTypes.bool, trackScroll: PropTypes.bool,
shouldUpdateScroll: PropTypes.func,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
showLoading: PropTypes.bool, showLoading: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
@ -264,11 +263,6 @@ class ScrollableList extends PureComponent {
this.props.onLoadMore(); this.props.onLoadMore();
} }
defaultShouldUpdateScroll = (prevRouterProps, { location }) => {
if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
return !(location.state && location.state.mastodonModalOpen);
}
handleLoadPending = e => { handleLoadPending = e => {
e.preventDefault(); e.preventDefault();
this.props.onLoadPending(); this.props.onLoadPending();
@ -282,7 +276,7 @@ class ScrollableList extends PureComponent {
} }
render () { 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 { fullscreen } = this.state;
const childrenCount = React.Children.count(children); const childrenCount = React.Children.count(children);
@ -348,7 +342,7 @@ class ScrollableList extends PureComponent {
if (trackScroll) { if (trackScroll) {
return ( return (
<ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll || this.defaultShouldUpdateScroll}> <ScrollContainer scrollKey={scrollKey}>
{scrollableArea} {scrollableArea}
</ScrollContainer> </ScrollContainer>
); );

View file

@ -147,7 +147,7 @@ class StatusActionBar extends ImmutablePureComponent {
handleOpen = () => { handleOpen = () => {
let state = {...this.context.router.history.location.state}; let state = {...this.context.router.history.location.state};
if (state.mastodonModalOpen) { if (state.mastodonModalKey) {
this.context.router.history.replace(`/statuses/${this.props.status.get('id')}`, { mastodonBackSteps: (state.mastodonBackSteps || 0) + 1 }); this.context.router.history.replace(`/statuses/${this.props.status.get('id')}`, { mastodonBackSteps: (state.mastodonBackSteps || 0) + 1 });
} else { } else {
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;

View file

@ -18,7 +18,6 @@ export default class StatusList extends ImmutablePureComponent {
onScrollToTop: PropTypes.func, onScrollToTop: PropTypes.func,
onScroll: PropTypes.func, onScroll: PropTypes.func,
trackScroll: PropTypes.bool, trackScroll: PropTypes.bool,
shouldUpdateScroll: PropTypes.func,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
isPartial: PropTypes.bool, isPartial: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,

View file

@ -41,7 +41,7 @@ export default class Mastodon extends React.PureComponent {
} }
shouldUpdateScroll (_, { location }) { shouldUpdateScroll (_, { location }) {
return !(location.state && location.state.mastodonModalOpen); return !(location.state?.mastodonModalKey);
} }
render () { render () {

View file

@ -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,
};
}

View file

@ -11,7 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { getAccountGallery } from 'flavours/glitch/selectors'; import { getAccountGallery } from 'flavours/glitch/selectors';
import MediaItem from './components/media_item'; import MediaItem from './components/media_item';
import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container';
import { ScrollContainer } from 'react-router-scroll-4'; import ScrollContainer from 'flavours/glitch/containers/scroll_container';
import LoadMore from 'flavours/glitch/components/load_more'; import LoadMore from 'flavours/glitch/components/load_more';
import MissingIndicator from 'flavours/glitch/components/missing_indicator'; import MissingIndicator from 'flavours/glitch/components/missing_indicator';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
@ -104,11 +104,6 @@ class AccountGallery extends ImmutablePureComponent {
this.handleScrollToBottom(); this.handleScrollToBottom();
} }
shouldUpdateScroll = (prevRouterProps, { location }) => {
if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
return !(location.state && location.state.mastodonModalOpen);
}
setColumnRef = c => { setColumnRef = c => {
this.column = c; this.column = c;
} }
@ -165,7 +160,7 @@ class AccountGallery extends ImmutablePureComponent {
<Column ref={this.setColumnRef}> <Column ref={this.setColumnRef}>
<ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} /> <ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
<ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={this.shouldUpdateScroll}> <ScrollContainer scrollKey='account_gallery'>
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}> <div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
<HeaderContainer accountId={this.props.params.accountId} /> <HeaderContainer accountId={this.props.params.accountId} />

View file

@ -12,7 +12,7 @@ import AccountCard from './components/account_card';
import RadioButton from 'flavours/glitch/components/radio_button'; import RadioButton from 'flavours/glitch/components/radio_button';
import classNames from 'classnames'; import classNames from 'classnames';
import LoadMore from 'flavours/glitch/components/load_more'; import LoadMore from 'flavours/glitch/components/load_more';
import { ScrollContainer } from 'react-router-scroll-4'; import ScrollContainer from 'flavours/glitch/containers/scroll_container';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.directory', defaultMessage: 'Browse profiles' }, title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
@ -40,7 +40,6 @@ class Directory extends React.PureComponent {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
accountIds: ImmutablePropTypes.list.isRequired, accountIds: ImmutablePropTypes.list.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
columnId: PropTypes.string, columnId: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
@ -125,7 +124,7 @@ class Directory extends React.PureComponent {
} }
render () { 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 { order, local } = this.getParams(this.props, this.state);
const pinned = !!columnId; const pinned = !!columnId;
@ -163,7 +162,7 @@ class Directory extends React.PureComponent {
multiColumn={multiColumn} multiColumn={multiColumn}
/> />
{multiColumn && !pinned ? <ScrollContainer scrollKey='directory' shouldUpdateScroll={shouldUpdateScroll}>{scrollableArea}</ScrollContainer> : scrollableArea} {multiColumn && !pinned ? <ScrollContainer scrollKey='directory'>{scrollableArea}</ScrollContainer> : scrollableArea}
</Column> </Column>
); );
} }

View file

@ -99,7 +99,6 @@ class Notifications extends React.PureComponent {
notifications: ImmutablePropTypes.list.isRequired, notifications: ImmutablePropTypes.list.isRequired,
showFilterBar: PropTypes.bool.isRequired, showFilterBar: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
isUnread: PropTypes.bool, isUnread: PropTypes.bool,
@ -220,7 +219,7 @@ class Notifications extends React.PureComponent {
} }
render () { 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 { notifCleaning, notifCleaningActive } = this.props; const { notifCleaning, notifCleaningActive } = this.props;
const { animatingNCD } = this.state; const { animatingNCD } = this.state;
const pinned = !!columnId; const pinned = !!columnId;
@ -273,7 +272,6 @@ class Notifications extends React.PureComponent {
onLoadPending={this.handleLoadPending} onLoadPending={this.handleLoadPending}
onScrollToTop={this.handleScrollToTop} onScrollToTop={this.handleScrollToTop}
onScroll={this.handleScroll} onScroll={this.handleScroll}
shouldUpdateScroll={shouldUpdateScroll}
bindToDocument={!multiColumn} bindToDocument={!multiColumn}
> >
{scrollableContent} {scrollableContent}

View file

@ -32,7 +32,7 @@ import { initBlockModal } from 'flavours/glitch/actions/blocks';
import { initReport } from 'flavours/glitch/actions/reports'; import { initReport } from 'flavours/glitch/actions/reports';
import { initBoostModal } from 'flavours/glitch/actions/boosts'; import { initBoostModal } from 'flavours/glitch/actions/boosts';
import { makeGetStatus } from 'flavours/glitch/selectors'; import { makeGetStatus } from 'flavours/glitch/selectors';
import { ScrollContainer } from 'react-router-scroll-4'; import ScrollContainer from 'flavours/glitch/containers/scroll_container';
import ColumnBackButton from 'flavours/glitch/components/column_back_button'; import ColumnBackButton from 'flavours/glitch/components/column_back_button';
import ColumnHeader from '../../components/column_header'; import ColumnHeader from '../../components/column_header';
import StatusContainer from 'flavours/glitch/containers/status_container'; import StatusContainer from 'flavours/glitch/containers/status_container';
@ -507,11 +507,6 @@ class Status extends ImmutablePureComponent {
this.setState({ fullscreen: isFullscreen() }); this.setState({ fullscreen: isFullscreen() });
} }
shouldUpdateScroll = (prevRouterProps, { location }) => {
if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false;
return !(location.state && location.state.mastodonModalOpen);
}
render () { render () {
let ancestors, descendants; let ancestors, descendants;
const { setExpansion } = this; const { setExpansion } = this;
@ -562,7 +557,7 @@ class Status extends ImmutablePureComponent {
)} )}
/> />
<ScrollContainer scrollKey='thread' shouldUpdateScroll={this.shouldUpdateScroll}> <ScrollContainer scrollKey='thread'>
<div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}> <div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}>
{ancestors} {ancestors}

View file

@ -59,12 +59,8 @@ export default class ModalRoot extends React.PureComponent {
backgroundColor: null, backgroundColor: null,
}; };
getSnapshotBeforeUpdate () { componentDidUpdate () {
return { visible: !!this.props.type }; if (!!this.props.type) {
}
componentDidUpdate (prevProps, prevState, { visible }) {
if (visible) {
document.body.classList.add('with-modals--active'); document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
} else { } else {

View file

@ -3,8 +3,8 @@ import { closeModal } from 'flavours/glitch/actions/modal';
import ModalRoot from '../components/modal_root'; import ModalRoot from '../components/modal_root';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
type: state.get('modal').modalType, type: state.getIn(['modal', 0, 'modalType'], null),
props: state.get('modal').modalProps, props: state.getIn(['modal', 0, 'modalProps'], {}),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View file

@ -212,7 +212,7 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/start' component={FollowRecommendations} content={children} /> <WrappedRoute path='/start' component={FollowRecommendations} content={children} />
<WrappedRoute path='/search' component={Search} content={children} /> <WrappedRoute path='/search' component={Search} content={children} />
<WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/directory' component={Directory} content={children} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} /> <WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} /> <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />

View file

@ -1,19 +1,15 @@
import { MODAL_OPEN, MODAL_CLOSE } from 'flavours/glitch/actions/modal'; import { MODAL_OPEN, MODAL_CLOSE } from 'flavours/glitch/actions/modal';
import { TIMELINE_DELETE } from 'flavours/glitch/actions/timelines'; import { TIMELINE_DELETE } from 'flavours/glitch/actions/timelines';
import { Stack as ImmutableStack, Map as ImmutableMap } from 'immutable';
const initialState = { export default function modal(state = ImmutableStack(), action) {
modalType: null,
modalProps: {},
};
export default function modal(state = initialState, action) {
switch(action.type) { switch(action.type) {
case MODAL_OPEN: case MODAL_OPEN:
return { modalType: action.modalType, modalProps: action.modalProps }; return state.unshift(ImmutableMap({ modalType: action.modalType, modalProps: action.modalProps }));
case MODAL_CLOSE: 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: case TIMELINE_DELETE:
return (state.modalProps.statusId === action.id) ? initialState : state; return state.filterNot((modal) => modal.get('modalProps').statusId === action.id);
default: default:
return state; return state;
} }