[Glitch] Freeze scroll position when a dropdown menu is open in the TL

Port 6fda3cbbeb to glitch-soc

Signed-off-by: Thibaut Girka <thib@sitedethib.com>
This commit is contained in:
ThibG 2020-07-09 15:09:19 +02:00 committed by Thibaut Girka
parent 042c32ea3b
commit e248399220
9 changed files with 48 additions and 15 deletions

View File

@ -1,8 +1,8 @@
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN'; export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE'; export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
export function openDropdownMenu(id, placement, keyboard) { export function openDropdownMenu(id, placement, keyboard, scroll_key) {
return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard }; return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard, scroll_key };
} }
export function closeDropdownMenu(id) { export function closeDropdownMenu(id) {

View File

@ -10,10 +10,18 @@ import { List as ImmutableList } from 'immutable';
import classNames from 'classnames'; import classNames from 'classnames';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen';
import LoadingIndicator from './loading_indicator'; import LoadingIndicator from './loading_indicator';
import { connect } from 'react-redux';
const MOUSE_IDLE_DELAY = 300; const MOUSE_IDLE_DELAY = 300;
export default class ScrollableList extends PureComponent { const mapStateToProps = (state, { scrollKey }) => {
return {
preventScroll: scrollKey === state.getIn(['dropdown_menu', 'scroll_key']),
};
};
export default @connect(mapStateToProps)
class ScrollableList extends PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@ -37,6 +45,7 @@ export default class ScrollableList extends PureComponent {
emptyMessage: PropTypes.node, emptyMessage: PropTypes.node,
children: PropTypes.node, children: PropTypes.node,
bindToDocument: PropTypes.bool, bindToDocument: PropTypes.bool,
preventScroll: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
@ -124,7 +133,7 @@ export default class ScrollableList extends PureComponent {
}); });
handleMouseIdle = () => { handleMouseIdle = () => {
if (this.scrollToTopOnMouseIdle) { if (this.scrollToTopOnMouseIdle && !this.props.preventScroll) {
this.setScrollTop(0); this.setScrollTop(0);
} }
this.mouseMovedRecently = false; this.mouseMovedRecently = false;
@ -176,7 +185,7 @@ export default class ScrollableList extends PureComponent {
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props); this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
const pendingChanged = (prevProps.numPending > 0) !== (this.props.numPending > 0); const pendingChanged = (prevProps.numPending > 0) !== (this.props.numPending > 0);
if (pendingChanged || someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently)) { if (pendingChanged || someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently || this.props.preventScroll)) {
return this.getScrollHeight() - this.getScrollTop(); return this.getScrollHeight() - this.getScrollTop();
} else { } else {
return null; return null;

View File

@ -96,6 +96,7 @@ class Status extends ImmutablePureComponent {
cacheMediaWidth: PropTypes.func, cacheMediaWidth: PropTypes.func,
cachedMediaWidth: PropTypes.number, cachedMediaWidth: PropTypes.number,
onClick: PropTypes.func, onClick: PropTypes.func,
scrollKey: PropTypes.string,
}; };
state = { state = {

View File

@ -74,6 +74,7 @@ class StatusActionBar extends ImmutablePureComponent {
withDismiss: PropTypes.bool, withDismiss: PropTypes.bool,
showReplyCount: PropTypes.bool, showReplyCount: PropTypes.bool,
directMessage: PropTypes.bool, directMessage: PropTypes.bool,
scrollKey: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -198,7 +199,7 @@ class StatusActionBar extends ImmutablePureComponent {
} }
render () { render () {
const { status, intl, withDismiss, showReplyCount, directMessage } = this.props; const { status, intl, withDismiss, showReplyCount, directMessage, scrollKey } = this.props;
const mutingConversation = status.get('muted'); const mutingConversation = status.get('muted');
const anonymousAccess = !me; const anonymousAccess = !me;
@ -300,7 +301,16 @@ class StatusActionBar extends ImmutablePureComponent {
<IconButton key='bookmark-button' className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />, <IconButton key='bookmark-button' className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />,
filterButton, filterButton,
<div key='dropdown-button' className='status__action-bar-dropdown'> <div key='dropdown-button' className='status__action-bar-dropdown'>
<DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel={intl.formatMessage(messages.more)} /> <DropdownMenuContainer
scrollKey={scrollKey}
disabled={anonymousAccess}
status={status}
items={menu}
icon='ellipsis-h'
size={18}
direction='right'
ariaLabel={intl.formatMessage(messages.more)}
/>
</div>, </div>,
]} ]}

View File

@ -99,6 +99,7 @@ export default class StatusList extends ImmutablePureComponent {
onMoveUp={this.handleMoveUp} onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown} onMoveDown={this.handleMoveDown}
contextType={timelineId} contextType={timelineId}
scrollKey={this.props.scrollKey}
/> />
)) ))
) : null; ) : null;
@ -112,6 +113,7 @@ export default class StatusList extends ImmutablePureComponent {
onMoveUp={this.handleMoveUp} onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown} onMoveDown={this.handleMoveDown}
contextType={timelineId} contextType={timelineId}
scrollKey={this.props.scrollKey}
/> />
)).concat(scrollableContent); )).concat(scrollableContent);
} }

View File

@ -11,7 +11,7 @@ const mapStateToProps = state => ({
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']), openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
}); });
const mapDispatchToProps = (dispatch, { status, items }) => ({ const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
onOpen(id, onItemClick, dropdownPlacement, keyboard) { onOpen(id, onItemClick, dropdownPlacement, keyboard) {
dispatch(isUserTouching() ? openModal('ACTIONS', { dispatch(isUserTouching() ? openModal('ACTIONS', {
status, status,
@ -22,7 +22,7 @@ const mapDispatchToProps = (dispatch, { status, items }) => ({
onClick: item.action ? ((e) => { return onItemClick(i, e) }) : null, onClick: item.action ? ((e) => { return onItemClick(i, e) }) : null,
} : null } : null
), ),
}) : openDropdownMenu(id, dropdownPlacement, keyboard)); }) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
}, },
onClose(id) { onClose(id) {
dispatch(closeModal('ACTIONS')); dispatch(closeModal('ACTIONS'));

View File

@ -36,6 +36,7 @@ class Conversation extends ImmutablePureComponent {
accounts: ImmutablePropTypes.list.isRequired, accounts: ImmutablePropTypes.list.isRequired,
lastStatus: ImmutablePropTypes.map, lastStatus: ImmutablePropTypes.map,
unread:PropTypes.bool.isRequired, unread:PropTypes.bool.isRequired,
scrollKey: PropTypes.string,
onMoveUp: PropTypes.func, onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func, onMoveDown: PropTypes.func,
markRead: PropTypes.func.isRequired, markRead: PropTypes.func.isRequired,
@ -156,7 +157,7 @@ class Conversation extends ImmutablePureComponent {
} }
render () { render () {
const { accounts, lastStatus, unread, intl } = this.props; const { accounts, lastStatus, unread, scrollKey, intl } = this.props;
const { isExpanded } = this.state; const { isExpanded } = this.state;
if (lastStatus === null) { if (lastStatus === null) {
@ -223,7 +224,15 @@ class Conversation extends ImmutablePureComponent {
<IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReply} /> <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReply} />
<div className='status__action-bar-dropdown'> <div className='status__action-bar-dropdown'>
<DropdownMenuContainer status={lastStatus} items={menu} icon='ellipsis-h' size={18} direction='right' title={intl.formatMessage(messages.more)} /> <DropdownMenuContainer
scrollKey={scrollKey}
status={lastStatus}
items={menu}
icon='ellipsis-h'
size={18}
direction='right'
title={intl.formatMessage(messages.more)}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -10,6 +10,7 @@ export default class ConversationsList extends ImmutablePureComponent {
static propTypes = { static propTypes = {
conversations: ImmutablePropTypes.list.isRequired, conversations: ImmutablePropTypes.list.isRequired,
scrollKey: PropTypes.string.isRequired,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
onLoadMore: PropTypes.func, onLoadMore: PropTypes.func,
@ -57,13 +58,14 @@ export default class ConversationsList extends ImmutablePureComponent {
const { conversations, onLoadMore, ...other } = this.props; const { conversations, onLoadMore, ...other } = this.props;
return ( return (
<ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} scrollKey='direct' ref={this.setRef}> <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
{conversations.map(item => ( {conversations.map(item => (
<ConversationContainer <ConversationContainer
key={item.get('id')} key={item.get('id')}
conversationId={item.get('id')} conversationId={item.get('id')}
onMoveUp={this.handleMoveUp} onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown} onMoveDown={this.handleMoveDown}
scrollKey={this.props.scrollKey}
/> />
))} ))}
</ScrollableList> </ScrollableList>

View File

@ -4,14 +4,14 @@ import {
DROPDOWN_MENU_CLOSE, DROPDOWN_MENU_CLOSE,
} from '../actions/dropdown_menu'; } from '../actions/dropdown_menu';
const initialState = Immutable.Map({ openId: null, placement: null, keyboard: false }); const initialState = Immutable.Map({ openId: null, placement: null, keyboard: false, scroll_key: null });
export default function dropdownMenu(state = initialState, action) { export default function dropdownMenu(state = initialState, action) {
switch (action.type) { switch (action.type) {
case DROPDOWN_MENU_OPEN: case DROPDOWN_MENU_OPEN:
return state.merge({ openId: action.id, placement: action.placement, keyboard: action.keyboard }); return state.merge({ openId: action.id, placement: action.placement, keyboard: action.keyboard, scroll_key: action.scroll_key });
case DROPDOWN_MENU_CLOSE: case DROPDOWN_MENU_CLOSE:
return state.get('openId') === action.id ? state.set('openId', null) : state; return state.get('openId') === action.id ? state.set('openId', null).set('scroll_key', null) : state;
default: default:
return state; return state;
} }