Skip to content

Commit

Permalink
Change single-column mode to scroll the whole page (mastodon#11359)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gargron authored Jul 19, 2019
1 parent 4fa6472 commit aa22b38
Show file tree
Hide file tree
Showing 25 changed files with 162 additions and 42 deletions.
58 changes: 44 additions & 14 deletions app/javascript/mastodon/components/scrollable_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default class ScrollableList extends PureComponent {
alwaysPrepend: PropTypes.bool,
emptyMessage: PropTypes.node,
children: PropTypes.node,
bindToDocument: PropTypes.bool,
};

static defaultProps = {
Expand All @@ -50,7 +51,9 @@ export default class ScrollableList extends PureComponent {

handleScroll = throttle(() => {
if (this.node) {
const { scrollTop, scrollHeight, clientHeight } = this.node;
const scrollTop = this.getScrollTop();
const scrollHeight = this.getScrollHeight();
const clientHeight = this.getClientHeight();
const offset = scrollHeight - scrollTop - clientHeight;

if (400 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
Expand Down Expand Up @@ -80,9 +83,14 @@ export default class ScrollableList extends PureComponent {
scrollToTopOnMouseIdle = false;

setScrollTop = newScrollTop => {
if (this.node.scrollTop !== newScrollTop) {
if (this.getScrollTop() !== newScrollTop) {
this.lastScrollWasSynthetic = true;
this.node.scrollTop = newScrollTop;

if (this.props.bindToDocument) {
document.scrollingElement.scrollTop = newScrollTop;
} else {
this.node.scrollTop = newScrollTop;
}
}
};

Expand All @@ -100,7 +108,7 @@ export default class ScrollableList extends PureComponent {
this.clearMouseIdleTimer();
this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);

if (!this.mouseMovedRecently && this.node.scrollTop === 0) {
if (!this.mouseMovedRecently && this.getScrollTop() === 0) {
// Only set if we just started moving and are scrolled to the top.
this.scrollToTopOnMouseIdle = true;
}
Expand Down Expand Up @@ -135,15 +143,27 @@ export default class ScrollableList extends PureComponent {
}

getScrollPosition = () => {
if (this.node && (this.node.scrollTop > 0 || this.mouseMovedRecently)) {
return { height: this.node.scrollHeight, top: this.node.scrollTop };
if (this.node && (this.getScrollTop() > 0 || this.mouseMovedRecently)) {
return { height: this.getScrollHeight(), top: this.getScrollTop() };
} else {
return null;
}
}

getScrollTop = () => {
return this.props.bindToDocument ? document.scrollingElement.scrollTop : this.node.scrollTop;
}

getScrollHeight = () => {
return this.props.bindToDocument ? document.scrollingElement.scrollHeight : this.node.scrollHeight;
}

getClientHeight = () => {
return this.props.bindToDocument ? document.scrollingElement.clientHeight : this.node.clientHeight;
}

updateScrollBottom = (snapshot) => {
const newScrollTop = this.node.scrollHeight - snapshot;
const newScrollTop = this.getScrollHeight() - snapshot;

this.setScrollTop(newScrollTop);
}
Expand All @@ -153,8 +173,8 @@ export default class ScrollableList extends PureComponent {
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);

if (someItemInserted && (this.node.scrollTop > 0 || this.mouseMovedRecently)) {
return this.node.scrollHeight - this.node.scrollTop;
if (someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently)) {
return this.getScrollHeight() - this.getScrollTop();
} else {
return null;
}
Expand All @@ -164,7 +184,7 @@ export default class ScrollableList extends PureComponent {
// Reset the scroll position when a new child comes in in order not to
// jerk the scrollbar around if you're already scrolled down the page.
if (snapshot !== null) {
this.setScrollTop(this.node.scrollHeight - snapshot);
this.setScrollTop(this.getScrollHeight() - snapshot);
}
}

Expand Down Expand Up @@ -197,13 +217,23 @@ export default class ScrollableList extends PureComponent {
}

attachScrollListener () {
this.node.addEventListener('scroll', this.handleScroll);
this.node.addEventListener('wheel', this.handleWheel);
if (this.props.bindToDocument) {
document.addEventListener('scroll', this.handleScroll);
document.addEventListener('wheel', this.handleWheel);
} else {
this.node.addEventListener('scroll', this.handleScroll);
this.node.addEventListener('wheel', this.handleWheel);
}
}

detachScrollListener () {
this.node.removeEventListener('scroll', this.handleScroll);
this.node.removeEventListener('wheel', this.handleWheel);
if (this.props.bindToDocument) {
document.removeEventListener('scroll', this.handleScroll);
document.removeEventListener('wheel', this.handleWheel);
} else {
this.node.removeEventListener('scroll', this.handleScroll);
this.node.removeEventListener('wheel', this.handleWheel);
}
}

getFirstChildKey (props) {
Expand Down
7 changes: 7 additions & 0 deletions app/javascript/mastodon/containers/media_container.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Video from '../features/video';
import Card from '../features/status/components/card';
import Poll from 'mastodon/components/poll';
import ModalRoot from '../components/modal_root';
import { getScrollbarWidth } from '../features/ui/components/modal_root';
import MediaModal from '../features/ui/components/media_modal';
import { List as ImmutableList, fromJS } from 'immutable';

Expand All @@ -31,18 +32,24 @@ export default class MediaContainer extends PureComponent {

handleOpenMedia = (media, index) => {
document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;

this.setState({ media, index });
}

handleOpenVideo = (video, time) => {
const media = ImmutableList([video]);

document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;

this.setState({ media, time });
}

handleCloseMedia = () => {
document.body.classList.remove('with-modals--active');
document.documentElement.style.marginRight = 0;

this.setState({ media: null, index: null, time: null });
}

Expand Down
4 changes: 3 additions & 1 deletion app/javascript/mastodon/features/account_timeline/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class AccountTimeline extends ImmutablePureComponent {
withReplies: PropTypes.bool,
blockedBy: PropTypes.bool,
isAccount: PropTypes.bool,
multiColumn: PropTypes.bool,
};

componentWillMount () {
Expand Down Expand Up @@ -77,7 +78,7 @@ class AccountTimeline extends ImmutablePureComponent {
}

render () {
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount } = this.props;
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount, multiColumn } = this.props;

if (!isAccount) {
return (
Expand Down Expand Up @@ -112,6 +113,7 @@ class AccountTimeline extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
/>
</Column>
);
Expand Down
4 changes: 3 additions & 1 deletion app/javascript/mastodon/features/blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Blocks extends ImmutablePureComponent {
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
};

componentWillMount () {
Expand All @@ -43,7 +44,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true });

render () {
const { intl, accountIds, shouldUpdateScroll, hasMore } = this.props;
const { intl, accountIds, shouldUpdateScroll, hasMore, multiColumn } = this.props;

if (!accountIds) {
return (
Expand All @@ -64,6 +65,7 @@ class Blocks extends ImmutablePureComponent {
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{accountIds.map(id =>
<AccountContainer key={id} id={id} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class CommunityTimeline extends React.PureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
shouldUpdateScroll={shouldUpdateScroll}
bindToDocument={!multiColumn}
/>
</Column>
);
Expand Down
4 changes: 3 additions & 1 deletion app/javascript/mastodon/features/domain_blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Blocks extends ImmutablePureComponent {
hasMore: PropTypes.bool,
domains: ImmutablePropTypes.orderedSet,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
};

componentWillMount () {
Expand All @@ -44,7 +45,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true });

render () {
const { intl, domains, shouldUpdateScroll, hasMore } = this.props;
const { intl, domains, shouldUpdateScroll, hasMore, multiColumn } = this.props;

if (!domains) {
return (
Expand All @@ -65,6 +66,7 @@ class Blocks extends ImmutablePureComponent {
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{domains.map(domain =>
<DomainContainer key={domain} domain={domain} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class Favourites extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
/>
</Column>
);
Expand Down
4 changes: 3 additions & 1 deletion app/javascript/mastodon/features/favourites/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Favourites extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
};

componentWillMount () {
Expand All @@ -36,7 +37,7 @@ class Favourites extends ImmutablePureComponent {
}

render () {
const { shouldUpdateScroll, accountIds } = this.props;
const { shouldUpdateScroll, accountIds, multiColumn } = this.props;

if (!accountIds) {
return (
Expand All @@ -56,6 +57,7 @@ class Favourites extends ImmutablePureComponent {
scrollKey='favourites'
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
Expand Down
4 changes: 3 additions & 1 deletion app/javascript/mastodon/features/follow_requests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class FollowRequests extends ImmutablePureComponent {
hasMore: PropTypes.bool,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
};

componentWillMount () {
Expand All @@ -43,7 +44,7 @@ class FollowRequests extends ImmutablePureComponent {
}, 300, { leading: true });

render () {
const { intl, shouldUpdateScroll, accountIds, hasMore } = this.props;
const { intl, shouldUpdateScroll, accountIds, hasMore, multiColumn } = this.props;

if (!accountIds) {
return (
Expand All @@ -64,6 +65,7 @@ class FollowRequests extends ImmutablePureComponent {
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{accountIds.map(id =>
<AccountAuthorizeContainer key={id} id={id} />
Expand Down
4 changes: 3 additions & 1 deletion app/javascript/mastodon/features/followers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Followers extends ImmutablePureComponent {
hasMore: PropTypes.bool,
blockedBy: PropTypes.bool,
isAccount: PropTypes.bool,
multiColumn: PropTypes.bool,
};

componentWillMount () {
Expand All @@ -55,7 +56,7 @@ class Followers extends ImmutablePureComponent {
}, 300, { leading: true });

render () {
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props;
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn } = this.props;

if (!isAccount) {
return (
Expand Down Expand Up @@ -87,6 +88,7 @@ class Followers extends ImmutablePureComponent {
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
alwaysPrepend
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{blockedBy ? [] : accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
Expand Down
4 changes: 3 additions & 1 deletion app/javascript/mastodon/features/following/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Following extends ImmutablePureComponent {
hasMore: PropTypes.bool,
blockedBy: PropTypes.bool,
isAccount: PropTypes.bool,
multiColumn: PropTypes.bool,
};

componentWillMount () {
Expand All @@ -55,7 +56,7 @@ class Following extends ImmutablePureComponent {
}, 300, { leading: true });

render () {
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props;
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn } = this.props;

if (!isAccount) {
return (
Expand Down Expand Up @@ -87,6 +88,7 @@ class Following extends ImmutablePureComponent {
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
alwaysPrepend
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{blockedBy ? [] : accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
Expand Down
1 change: 1 addition & 0 deletions app/javascript/mastodon/features/hashtag_timeline/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class HashtagTimeline extends React.PureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
shouldUpdateScroll={shouldUpdateScroll}
bindToDocument={!multiColumn}
/>
</Column>
);
Expand Down
1 change: 1 addition & 0 deletions app/javascript/mastodon/features/home_timeline/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class HomeTimeline extends React.PureComponent {
timelineId='home'
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />}
shouldUpdateScroll={shouldUpdateScroll}
bindToDocument={!multiColumn}
/>
</Column>
);
Expand Down
1 change: 1 addition & 0 deletions app/javascript/mastodon/features/list_timeline/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class ListTimeline extends React.PureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
shouldUpdateScroll={shouldUpdateScroll}
bindToDocument={!multiColumn}
/>
</Column>
);
Expand Down
4 changes: 3 additions & 1 deletion app/javascript/mastodon/features/lists/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ class Lists extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
lists: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
};

componentWillMount () {
this.props.dispatch(fetchLists());
}

render () {
const { intl, shouldUpdateScroll, lists } = this.props;
const { intl, shouldUpdateScroll, lists, multiColumn } = this.props;

if (!lists) {
return (
Expand All @@ -70,6 +71,7 @@ class Lists extends ImmutablePureComponent {
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
bindToDocument={!multiColumn}
>
{lists.map(list =>
<ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
Expand Down
Loading

0 comments on commit aa22b38

Please sign in to comment.