From e91bf82083ac390a0cf229d8e94fa412fdec57ff Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 16 Jul 2019 06:30:47 +0200 Subject: [Glitch] Add option to disable real-time updates in web UI Port 729723f857d11434c0f78d63fe16537d77f1c77c to glitch-soc Signed-off-by: Thibaut Girka --- .../flavours/glitch/components/scrollable_list.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch/components/scrollable_list.js') diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js index 462185bbc..5f42bdd8b 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.js +++ b/app/javascript/flavours/glitch/components/scrollable_list.js @@ -3,6 +3,7 @@ import { ScrollContainer } from 'react-router-scroll-4'; import PropTypes from 'prop-types'; import IntersectionObserverArticleContainer from 'flavours/glitch/containers/intersection_observer_article_container'; import LoadMore from './load_more'; +import LoadPending from './load_pending'; import IntersectionObserverWrapper from 'flavours/glitch/util/intersection_observer_wrapper'; import { throttle } from 'lodash'; import { List as ImmutableList } from 'immutable'; @@ -21,6 +22,7 @@ export default class ScrollableList extends PureComponent { static propTypes = { scrollKey: PropTypes.string.isRequired, onLoadMore: PropTypes.func, + onLoadPending: PropTypes.func, onScrollToTop: PropTypes.func, onScroll: PropTypes.func, trackScroll: PropTypes.bool, @@ -28,6 +30,7 @@ export default class ScrollableList extends PureComponent { isLoading: PropTypes.bool, showLoading: PropTypes.bool, hasMore: PropTypes.bool, + numPending: PropTypes.number, prepend: PropTypes.node, alwaysPrepend: PropTypes.bool, emptyMessage: PropTypes.node, @@ -222,12 +225,18 @@ export default class ScrollableList extends PureComponent { return !(location.state && location.state.mastodonModalOpen); } + handleLoadPending = e => { + e.preventDefault(); + this.props.onLoadPending(); + } + render () { - const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props; + const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props; const { fullscreen } = this.state; const childrenCount = React.Children.count(children); const loadMore = (hasMore && onLoadMore) ? : null; + const loadPending = (numPending > 0) ? : null; let scrollableArea = null; if (showLoading) { @@ -248,6 +257,8 @@ export default class ScrollableList extends PureComponent {
{prepend} + {loadPending} + {React.Children.map(this.props.children, (child, index) => ( Date: Mon, 16 Sep 2019 15:45:06 +0200 Subject: [Glitch] Fix “slow mode” issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port f1098675782944fe1dabec71cd58d3d091da06a0 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/components/scrollable_list.js | 11 ++++++++++- .../flavours/glitch/features/community_timeline/index.js | 2 +- .../flavours/glitch/features/notifications/index.js | 2 +- app/javascript/flavours/glitch/reducers/notifications.js | 6 ++++-- app/javascript/flavours/glitch/reducers/timelines.js | 4 ++-- 5 files changed, 18 insertions(+), 7 deletions(-) (limited to 'app/javascript/flavours/glitch/components/scrollable_list.js') diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js index 5f42bdd8b..7c0b6d082 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.js +++ b/app/javascript/flavours/glitch/components/scrollable_list.js @@ -153,7 +153,9 @@ export default class ScrollableList extends PureComponent { const someItemInserted = React.Children.count(prevProps.children) > 0 && 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)) { + const pendingChanged = (prevProps.numPending > 0) !== (this.props.numPending > 0); + + if (pendingChanged || someItemInserted && (this.node.scrollTop > 0 || this.mouseMovedRecently)) { return this.node.scrollHeight - this.node.scrollTop; } else { return null; @@ -228,6 +230,13 @@ export default class ScrollableList extends PureComponent { handleLoadPending = e => { e.preventDefault(); this.props.onLoadPending(); + // Prevent the weird scroll-jumping behavior, as we explicitly don't want to + // scroll to top, and we know the scroll height is going to change + this.scrollToTopOnMouseIdle = false; + this.lastScrollWasSynthetic = false; + this.clearMouseIdleTimer(); + this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY); + this.mouseMovedRecently = true; } render () { diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.js b/app/javascript/flavours/glitch/features/community_timeline/index.js index cb7d72c53..11b67fa6a 100644 --- a/app/javascript/flavours/glitch/features/community_timeline/index.js +++ b/app/javascript/flavours/glitch/features/community_timeline/index.js @@ -20,7 +20,7 @@ const mapStateToProps = (state, { onlyMedia, columnId }) => { const index = columns.findIndex(c => c.get('uuid') === uuid); return { - hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0, + hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0 || state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'pendingItems']).size > 0, onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']), }; }; diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index bd1af97a9..99b2391c7 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -47,7 +47,7 @@ const mapStateToProps = state => ({ notifications: getNotifications(state), localSettings: state.get('local_settings'), isLoading: state.getIn(['notifications', 'isLoading'], true), - isUnread: state.getIn(['notifications', 'unread']) > 0, + isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0, hasMore: state.getIn(['notifications', 'hasMore']), numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, notifCleaningActive: state.getIn(['notifications', 'cleaningMode']), diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 135995da6..2413f26fb 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -51,7 +51,7 @@ const notificationToMap = (state, notification) => ImmutableMap({ const normalizeNotification = (state, notification, usePendingItems) => { if (usePendingItems) { - return state.update('pendingItems', list => list.unshift(notificationToMap(state, notification))); + return state.update('pendingItems', list => list.unshift(notificationToMap(state, notification))).update('unread', unread => unread + 1); } const top = !shouldCountUnreadNotifications(state); @@ -117,7 +117,7 @@ const filterNotifications = (state, accountIds) => { }; const clearUnread = (state) => { - state = state.set('unread', 0); + state = state.set('unread', state.get('pendingItems').size); const lastNotification = state.get('items').find(item => item !== null); return state.set('lastReadId', lastNotification ? lastNotification.get('id') : '0'); } @@ -140,6 +140,8 @@ const deleteByStatus = (state, statusId) => { state = state.update('unread', unread => unread - deletedUnread.size); } const helper = list => list.filterNot(item => item !== null && item.get('status') === statusId); + const deletedUnread = state.get('pendingItems').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0); + state = state.update('unread', unread => unread - deletedUnread.size); return state.update('items', helper).update('pendingItems', helper); }; diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js index 9b016a4c6..7fb0a7fc1 100644 --- a/app/javascript/flavours/glitch/reducers/timelines.js +++ b/app/javascript/flavours/glitch/reducers/timelines.js @@ -64,7 +64,7 @@ const updateTimeline = (state, timeline, status, usePendingItems) => { return state; } - return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id')))); + return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id'))).update('unread', unread => unread + 1)); } const top = state.getIn([timeline, 'top']); @@ -127,7 +127,7 @@ const filterTimeline = (timeline, state, relationship, statuses) => { const updateTop = (state, timeline, top) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { - if (top) mMap.set('unread', 0); + if (top) mMap.set('unread', mMap.get('pendingItems').size); mMap.set('top', top); })); }; -- cgit From 9e2e623ebe25b8e58a6c8f4bf947015481f10c66 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 19 Jul 2019 09:25:22 +0200 Subject: [Glitch] Change single-column mode to scroll the whole page Port aa22b38fdbc1842549b6cbc0e0d948f85a71b92a to glitch-soc Signed-off-by: Thibaut Girka --- .../flavours/glitch/components/scrollable_list.js | 60 +++++++++++++++++----- .../flavours/glitch/containers/media_container.js | 7 +++ .../glitch/features/account_timeline/index.js | 4 +- .../flavours/glitch/features/blocks/index.js | 4 +- .../glitch/features/community_timeline/index.js | 1 + .../glitch/features/domain_blocks/index.js | 4 +- .../glitch/features/favourited_statuses/index.js | 1 + .../flavours/glitch/features/favourites/index.js | 4 +- .../glitch/features/follow_requests/index.js | 4 +- .../flavours/glitch/features/followers/index.js | 4 +- .../flavours/glitch/features/following/index.js | 4 +- .../glitch/features/hashtag_timeline/index.js | 1 + .../glitch/features/home_timeline/index.js | 1 + .../glitch/features/list_timeline/index.js | 1 + .../flavours/glitch/features/lists/index.js | 4 +- .../flavours/glitch/features/mutes/index.js | 4 +- .../glitch/features/notifications/index.js | 1 + .../glitch/features/pinned_statuses/index.js | 4 +- .../glitch/features/public_timeline/index.js | 1 + .../flavours/glitch/features/reblogs/index.js | 4 +- .../glitch/features/ui/components/modal_root.js | 24 +++++++++ .../flavours/glitch/features/ui/index.js | 15 +++++- app/javascript/flavours/glitch/packs/public.js | 8 --- app/javascript/flavours/glitch/styles/basics.scss | 20 +++++--- .../flavours/glitch/styles/components/columns.scss | 3 +- .../glitch/styles/components/single_column.scss | 4 ++ 26 files changed, 150 insertions(+), 42 deletions(-) (limited to 'app/javascript/flavours/glitch/components/scrollable_list.js') diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js index 7c0b6d082..c022290a4 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.js +++ b/app/javascript/flavours/glitch/components/scrollable_list.js @@ -35,6 +35,7 @@ export default class ScrollableList extends PureComponent { alwaysPrepend: PropTypes.bool, emptyMessage: PropTypes.node, children: PropTypes.node, + bindToDocument: PropTypes.bool, }; static defaultProps = { @@ -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) { @@ -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; + } } }; @@ -100,7 +108,7 @@ export default class ScrollableList extends PureComponent { 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; } @@ -132,15 +140,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); } @@ -155,8 +175,8 @@ export default class ScrollableList extends PureComponent { this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props); const pendingChanged = (prevProps.numPending > 0) !== (this.props.numPending > 0); - if (pendingChanged || someItemInserted && (this.node.scrollTop > 0 || this.mouseMovedRecently)) { - return this.node.scrollHeight - this.node.scrollTop; + if (pendingChanged || someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently)) { + return this.getScrollHeight() - this.getScrollTop(); } else { return null; } @@ -165,7 +185,9 @@ export default class ScrollableList extends PureComponent { componentDidUpdate (prevProps, prevState, snapshot) { // 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.updateScrollBottom(snapshot); + if (snapshot !== null) { + this.updateScrollBottom(snapshot); + } } componentWillUnmount () { @@ -191,13 +213,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) { diff --git a/app/javascript/flavours/glitch/containers/media_container.js b/app/javascript/flavours/glitch/containers/media_container.js index 41547412e..0afe50740 100644 --- a/app/javascript/flavours/glitch/containers/media_container.js +++ b/app/javascript/flavours/glitch/containers/media_container.js @@ -10,6 +10,7 @@ import Poll from 'flavours/glitch/components/poll'; import Hashtag from 'flavours/glitch/components/hashtag'; import Audio from 'flavours/glitch/features/audio'; import ModalRoot from 'flavours/glitch/components/modal_root'; +import { getScrollbarWidth } from 'flavours/glitch/features/ui/components/modal_root'; import MediaModal from 'flavours/glitch/features/ui/components/media_modal'; import { List as ImmutableList, fromJS } from 'immutable'; @@ -33,6 +34,8 @@ 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 }); } @@ -40,11 +43,15 @@ export default class MediaContainer extends PureComponent { 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 }); } diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index 2f0859341..a6ed4564c 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -39,6 +39,7 @@ class AccountTimeline extends ImmutablePureComponent { hasMore: PropTypes.bool, withReplies: PropTypes.bool, isAccount: PropTypes.bool, + multiColumn: PropTypes.bool, }; componentWillMount () { @@ -76,7 +77,7 @@ class AccountTimeline extends ImmutablePureComponent { } render () { - const { statusIds, featuredStatusIds, isLoading, hasMore, isAccount } = this.props; + const { statusIds, featuredStatusIds, isLoading, hasMore, isAccount, multiColumn } = this.props; if (!isAccount) { return ( @@ -108,6 +109,7 @@ class AccountTimeline extends ImmutablePureComponent { hasMore={hasMore} onLoadMore={this.handleLoadMore} emptyMessage={} + bindToDocument={!multiColumn} /> ); diff --git a/app/javascript/flavours/glitch/features/blocks/index.js b/app/javascript/flavours/glitch/features/blocks/index.js index 8a84f5a55..ae0cdf2fe 100644 --- a/app/javascript/flavours/glitch/features/blocks/index.js +++ b/app/javascript/flavours/glitch/features/blocks/index.js @@ -31,6 +31,7 @@ class Blocks extends ImmutablePureComponent { accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, }; componentWillMount () { @@ -42,7 +43,7 @@ class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, accountIds, hasMore } = this.props; + const { intl, accountIds, hasMore, multiColumn } = this.props; if (!accountIds) { return ( @@ -62,6 +63,7 @@ class Blocks extends ImmutablePureComponent { onLoadMore={this.handleLoadMore} hasMore={hasMore} emptyMessage={emptyMessage} + bindToDocument={!multiColumn} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.js b/app/javascript/flavours/glitch/features/community_timeline/index.js index 5585edc9c..ca437d2b0 100644 --- a/app/javascript/flavours/glitch/features/community_timeline/index.js +++ b/app/javascript/flavours/glitch/features/community_timeline/index.js @@ -125,6 +125,7 @@ class CommunityTimeline extends React.PureComponent { timelineId={`community${onlyMedia ? ':media' : ''}`} onLoadMore={this.handleLoadMore} emptyMessage={} + bindToDocument={!multiColumn} /> ); diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.js b/app/javascript/flavours/glitch/features/domain_blocks/index.js index 49e0368d7..b92ce349b 100644 --- a/app/javascript/flavours/glitch/features/domain_blocks/index.js +++ b/app/javascript/flavours/glitch/features/domain_blocks/index.js @@ -32,6 +32,7 @@ class Blocks extends ImmutablePureComponent { hasMore: PropTypes.bool, domains: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, }; componentWillMount () { @@ -43,7 +44,7 @@ class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, domains, hasMore } = this.props; + const { intl, domains, hasMore, multiColumn } = this.props; if (!domains) { return ( @@ -63,6 +64,7 @@ class Blocks extends ImmutablePureComponent { onLoadMore={this.handleLoadMore} hasMore={hasMore} emptyMessage={emptyMessage} + bindToDocument={!multiColumn} > {domains.map(domain => diff --git a/app/javascript/flavours/glitch/features/favourited_statuses/index.js b/app/javascript/flavours/glitch/features/favourited_statuses/index.js index 719a31d6e..99b532294 100644 --- a/app/javascript/flavours/glitch/features/favourited_statuses/index.js +++ b/app/javascript/flavours/glitch/features/favourited_statuses/index.js @@ -93,6 +93,7 @@ class Favourites extends ImmutablePureComponent { isLoading={isLoading} onLoadMore={this.handleLoadMore} emptyMessage={emptyMessage} + bindToDocument={!multiColumn} /> ); diff --git a/app/javascript/flavours/glitch/features/favourites/index.js b/app/javascript/flavours/glitch/features/favourites/index.js index 7afadf12e..3c0c2a905 100644 --- a/app/javascript/flavours/glitch/features/favourites/index.js +++ b/app/javascript/flavours/glitch/features/favourites/index.js @@ -27,6 +27,7 @@ class Favourites extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, + multiColumn: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -51,7 +52,7 @@ class Favourites extends ImmutablePureComponent { } render () { - const { intl, accountIds } = this.props; + const { intl, accountIds, multiColumn } = this.props; if (!accountIds) { return ( @@ -74,6 +75,7 @@ class Favourites extends ImmutablePureComponent { {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.js b/app/javascript/flavours/glitch/features/follow_requests/index.js index a7e8f4b61..04c1f3635 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.js +++ b/app/javascript/flavours/glitch/features/follow_requests/index.js @@ -31,6 +31,7 @@ class FollowRequests extends ImmutablePureComponent { hasMore: PropTypes.bool, accountIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, }; componentWillMount () { @@ -42,7 +43,7 @@ class FollowRequests extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, accountIds, hasMore } = this.props; + const { intl, accountIds, hasMore, multiColumn } = this.props; if (!accountIds) { return ( @@ -63,6 +64,7 @@ class FollowRequests extends ImmutablePureComponent { onLoadMore={this.handleLoadMore} hasMore={hasMore} emptyMessage={emptyMessage} + bindToDocument={!multiColumn} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index 2bd0e6e2f..39fdffae6 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -33,6 +33,7 @@ class Followers extends ImmutablePureComponent { accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, isAccount: PropTypes.bool, + multiColumn: PropTypes.bool, }; componentWillMount () { @@ -70,7 +71,7 @@ class Followers extends ImmutablePureComponent { } render () { - const { accountIds, hasMore, isAccount } = this.props; + const { accountIds, hasMore, isAccount, multiColumn } = this.props; if (!isAccount) { return ( @@ -101,6 +102,7 @@ class Followers extends ImmutablePureComponent { prepend={} alwaysPrepend emptyMessage={emptyMessage} + bindToDocument={!multiColumn} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index f03da0c94..493f86a66 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -33,6 +33,7 @@ class Following extends ImmutablePureComponent { accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, isAccount: PropTypes.bool, + multiColumn: PropTypes.bool, }; componentWillMount () { @@ -70,7 +71,7 @@ class Following extends ImmutablePureComponent { } render () { - const { accountIds, hasMore, isAccount } = this.props; + const { accountIds, hasMore, isAccount, multiColumn } = this.props; if (!isAccount) { return ( @@ -101,6 +102,7 @@ class Following extends ImmutablePureComponent { prepend={} alwaysPrepend emptyMessage={emptyMessage} + bindToDocument={!multiColumn} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js index d39505f46..b64b4bf13 100644 --- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js +++ b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js @@ -155,6 +155,7 @@ class HashtagTimeline extends React.PureComponent { timelineId={`hashtag:${id}`} onLoadMore={this.handleLoadMore} emptyMessage={} + bindToDocument={!multiColumn} /> ); diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js index defb1dcc1..b01c8cced 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/index.js +++ b/app/javascript/flavours/glitch/features/home_timeline/index.js @@ -117,6 +117,7 @@ class HomeTimeline extends React.PureComponent { onLoadMore={this.handleLoadMore} timelineId='home' emptyMessage={ }} />} + bindToDocument={!multiColumn} /> ); diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.js b/app/javascript/flavours/glitch/features/list_timeline/index.js index 8c3d0af51..f4b926e3c 100644 --- a/app/javascript/flavours/glitch/features/list_timeline/index.js +++ b/app/javascript/flavours/glitch/features/list_timeline/index.js @@ -212,6 +212,7 @@ class ListTimeline extends React.PureComponent { timelineId={`list:${id}`} onLoadMore={this.handleLoadMore} emptyMessage={} + bindToDocument={!multiColumn} /> ); diff --git a/app/javascript/flavours/glitch/features/lists/index.js b/app/javascript/flavours/glitch/features/lists/index.js index 79bf2e601..8d1b9d3ff 100644 --- a/app/javascript/flavours/glitch/features/lists/index.js +++ b/app/javascript/flavours/glitch/features/lists/index.js @@ -40,6 +40,7 @@ class Lists extends ImmutablePureComponent { dispatch: PropTypes.func.isRequired, lists: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, }; componentWillMount () { @@ -47,7 +48,7 @@ class Lists extends ImmutablePureComponent { } render () { - const { intl, lists } = this.props; + const { intl, lists, multiColumn } = this.props; if (!lists) { return ( @@ -69,6 +70,7 @@ class Lists extends ImmutablePureComponent { {lists.map(list => diff --git a/app/javascript/flavours/glitch/features/mutes/index.js b/app/javascript/flavours/glitch/features/mutes/index.js index 7c20ca9b9..e5a8010b4 100644 --- a/app/javascript/flavours/glitch/features/mutes/index.js +++ b/app/javascript/flavours/glitch/features/mutes/index.js @@ -31,6 +31,7 @@ class Mutes extends ImmutablePureComponent { hasMore: PropTypes.bool, accountIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, }; componentWillMount () { @@ -42,7 +43,7 @@ class Mutes extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, accountIds, hasMore } = this.props; + const { intl, accountIds, hasMore, multiColumn } = this.props; if (!accountIds) { return ( @@ -62,6 +63,7 @@ class Mutes extends ImmutablePureComponent { onLoadMore={this.handleLoadMore} hasMore={hasMore} emptyMessage={emptyMessage} + bindToDocument={!multiColumn} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index 99b2391c7..785a7dc51 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -226,6 +226,7 @@ class Notifications extends React.PureComponent { onScrollToTop={this.handleScrollToTop} onScroll={this.handleScroll} shouldUpdateScroll={shouldUpdateScroll} + bindToDocument={!multiColumn} > {scrollableContent} diff --git a/app/javascript/flavours/glitch/features/pinned_statuses/index.js b/app/javascript/flavours/glitch/features/pinned_statuses/index.js index 8d406ddf4..b0db90c2c 100644 --- a/app/javascript/flavours/glitch/features/pinned_statuses/index.js +++ b/app/javascript/flavours/glitch/features/pinned_statuses/index.js @@ -27,6 +27,7 @@ class PinnedStatuses extends ImmutablePureComponent { statusIds: ImmutablePropTypes.list.isRequired, intl: PropTypes.object.isRequired, hasMore: PropTypes.bool.isRequired, + multiColumn: PropTypes.bool, }; componentWillMount () { @@ -42,7 +43,7 @@ class PinnedStatuses extends ImmutablePureComponent { } render () { - const { intl, statusIds, hasMore } = this.props; + const { intl, statusIds, hasMore, multiColumn } = this.props; return ( @@ -51,6 +52,7 @@ class PinnedStatuses extends ImmutablePureComponent { statusIds={statusIds} scrollKey='pinned_statuses' hasMore={hasMore} + bindToDocument={!multiColumn} /> ); diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.js b/app/javascript/flavours/glitch/features/public_timeline/index.js index 4bcf3da9d..b64c634ac 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/index.js +++ b/app/javascript/flavours/glitch/features/public_timeline/index.js @@ -124,6 +124,7 @@ class PublicTimeline extends React.PureComponent { trackScroll={!pinned} scrollKey={`public_timeline-${columnId}`} emptyMessage={} + bindToDocument={!multiColumn} /> ); diff --git a/app/javascript/flavours/glitch/features/reblogs/index.js b/app/javascript/flavours/glitch/features/reblogs/index.js index a8e9db7f5..808b25b9e 100644 --- a/app/javascript/flavours/glitch/features/reblogs/index.js +++ b/app/javascript/flavours/glitch/features/reblogs/index.js @@ -27,6 +27,7 @@ class Reblogs extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, + multiColumn: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -51,7 +52,7 @@ class Reblogs extends ImmutablePureComponent { } render () { - const { intl, accountIds } = this.props; + const { intl, accountIds, multiColumn } = this.props; if (!accountIds) { return ( @@ -75,6 +76,7 @@ class Reblogs extends ImmutablePureComponent { {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.js index 117ce4c55..53334835d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js @@ -46,6 +46,28 @@ const MODAL_COMPONENTS = { 'PINNED_ACCOUNTS_EDITOR': PinnedAccountsEditor, }; +let cachedScrollbarWidth = null; + +export const getScrollbarWidth = () => { + if (cachedScrollbarWidth !== null) { + return cachedScrollbarWidth; + } + + const outer = document.createElement('div'); + outer.style.visibility = 'hidden'; + outer.style.overflow = 'scroll'; + document.body.appendChild(outer); + + const inner = document.createElement('div'); + outer.appendChild(inner); + + const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; + cachedScrollbarWidth = scrollbarWidth; + outer.parentNode.removeChild(outer); + + return scrollbarWidth; +}; + export default class ModalRoot extends React.PureComponent { static propTypes = { @@ -61,8 +83,10 @@ export default class ModalRoot extends React.PureComponent { componentDidUpdate (prevProps, prevState, { visible }) { if (visible) { document.body.classList.add('with-modals--active'); + document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; } else { document.body.classList.remove('with-modals--active'); + document.documentElement.style.marginRight = 0; } } diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index c201cd93d..e5925a484 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -129,12 +129,25 @@ class SwitchingColumnsArea extends React.PureComponent { componentWillMount () { window.addEventListener('resize', this.handleResize, { passive: true }); + + if (this.state.mobile) { + document.body.classList.toggle('layout-single-column', true); + document.body.classList.toggle('layout-multiple-columns', false); + } else { + document.body.classList.toggle('layout-single-column', false); + document.body.classList.toggle('layout-multiple-columns', true); + } } - componentDidUpdate (prevProps) { + componentDidUpdate (prevProps, prevState) { if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) { this.node.handleChildrenContentChange(); } + + if (prevState.mobile !== this.state.mobile) { + document.body.classList.toggle('layout-single-column', this.state.mobile); + document.body.classList.toggle('layout-multiple-columns', !this.state.mobile); + } } componentWillUnmount () { diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js index 5a15830df..767fb023c 100644 --- a/app/javascript/flavours/glitch/packs/public.js +++ b/app/javascript/flavours/glitch/packs/public.js @@ -94,14 +94,6 @@ function main() { new Rellax('.parallax', { speed: -1 }); } - if (document.body.classList.contains('with-modals')) { - const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; - const scrollbarWidthStyle = document.createElement('style'); - scrollbarWidthStyle.id = 'scrollbar-width'; - document.head.appendChild(scrollbarWidthStyle); - scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0); - } - delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); diff --git a/app/javascript/flavours/glitch/styles/basics.scss b/app/javascript/flavours/glitch/styles/basics.scss index 64e543b78..ed02118f0 100644 --- a/app/javascript/flavours/glitch/styles/basics.scss +++ b/app/javascript/flavours/glitch/styles/basics.scss @@ -7,7 +7,7 @@ body { font-family: $font-sans-serif, sans-serif; - background: darken($ui-base-color, 8%); + background: darken($ui-base-color, 7%); font-size: 13px; line-height: 18px; font-weight: 400; @@ -34,11 +34,19 @@ body { } &.app-body { - position: absolute; - width: 100%; - height: 100%; padding: 0; - background: $ui-base-color; + + &.layout-single-column { + height: auto; + min-height: 100%; + overflow-y: scroll; + } + + &.layout-multiple-columns { + position: absolute; + width: 100%; + height: 100%; + } &.with-modals--active { overflow-y: hidden; @@ -55,7 +63,6 @@ body { &--active { overflow-y: hidden; - margin-right: 13px; } } @@ -124,7 +131,6 @@ button { & > div { display: flex; width: 100%; - height: 100%; align-items: center; justify-content: center; outline: 0 !important; diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index 7f3c21163..32f587ca0 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -17,6 +17,7 @@ justify-content: center; width: 100%; height: 100%; + min-height: 100vh; &__pane { height: 100%; @@ -30,6 +31,7 @@ } &__inner { + position: fixed; width: 285px; pointer-events: auto; height: 100%; @@ -83,7 +85,6 @@ flex-direction: column; width: 100%; height: 100%; - background: darken($ui-base-color, 7%); } .column { diff --git a/app/javascript/flavours/glitch/styles/components/single_column.scss b/app/javascript/flavours/glitch/styles/components/single_column.scss index 1d8055fe5..fdee67ed1 100644 --- a/app/javascript/flavours/glitch/styles/components/single_column.scss +++ b/app/javascript/flavours/glitch/styles/components/single_column.scss @@ -127,6 +127,10 @@ top: 15px; } + .scrollable { + overflow: visible; + } + @media screen and (min-width: $no-gap-breakpoint) { padding: 10px 0; } -- cgit