diff --git a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx index fe8b883de5..c674c82547 100644 --- a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx +++ b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx @@ -15,13 +15,14 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col import ColumnHeader from 'flavours/glitch/components/column_header'; import StatusList from 'flavours/glitch/components/status_list'; import Column from 'flavours/glitch/features/ui/components/column'; +import { getStatusList } from 'flavours/glitch/selectors'; const messages = defineMessages({ heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, }); const mapStateToProps = state => ({ - statusIds: state.getIn(['status_lists', 'bookmarks', 'items']), + statusIds: getStatusList(state, 'bookmarks'), isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true), hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']), }); diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.jsx b/app/javascript/flavours/glitch/features/community_timeline/index.jsx index 127e7cf182..ca11adb464 100644 --- a/app/javascript/flavours/glitch/features/community_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/community_timeline/index.jsx @@ -142,11 +142,8 @@ class CommunityTimeline extends PureComponent { - - - - } trackScroll={!pinned} scrollKey={`community_timeline-${columnId}`} timelineId={`community${onlyMedia ? ':media' : ''}`} diff --git a/app/javascript/flavours/glitch/features/explore/links.jsx b/app/javascript/flavours/glitch/features/explore/links.jsx index ca7d948625..88976c4ea6 100644 --- a/app/javascript/flavours/glitch/features/explore/links.jsx +++ b/app/javascript/flavours/glitch/features/explore/links.jsx @@ -35,7 +35,7 @@ class Links extends PureComponent { const banner = ( - + ); diff --git a/app/javascript/flavours/glitch/features/explore/statuses.jsx b/app/javascript/flavours/glitch/features/explore/statuses.jsx index 212980c28f..ce484ef77c 100644 --- a/app/javascript/flavours/glitch/features/explore/statuses.jsx +++ b/app/javascript/flavours/glitch/features/explore/statuses.jsx @@ -11,9 +11,10 @@ import { debounce } from 'lodash'; import { fetchTrendingStatuses, expandTrendingStatuses } from 'flavours/glitch/actions/trends'; import DismissableBanner from 'flavours/glitch/components/dismissable_banner'; import StatusList from 'flavours/glitch/components/status_list'; +import { getStatusList } from 'flavours/glitch/selectors'; const mapStateToProps = state => ({ - statusIds: state.getIn(['status_lists', 'trending', 'items']), + statusIds: getStatusList(state, 'trending'), isLoading: state.getIn(['status_lists', 'trending', 'isLoading'], true), hasMore: !!state.getIn(['status_lists', 'trending', 'next']), }); @@ -46,7 +47,7 @@ class Statuses extends PureComponent { return ( <> - + - + ); diff --git a/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx b/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx index 08152063c7..ed11f2b8ce 100644 --- a/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx +++ b/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx @@ -15,13 +15,14 @@ import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'flavours/glit import ColumnHeader from 'flavours/glitch/components/column_header'; import StatusList from 'flavours/glitch/components/status_list'; import Column from 'flavours/glitch/features/ui/components/column'; +import { getStatusList } from 'flavours/glitch/selectors'; const messages = defineMessages({ heading: { id: 'column.favourites', defaultMessage: 'Favourites' }, }); const mapStateToProps = state => ({ - statusIds: state.getIn(['status_lists', 'favourites', 'items']), + statusIds: getStatusList(state, 'favourites'), isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true), hasMore: !!state.getIn(['status_lists', 'favourites', 'next']), }); diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx b/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx new file mode 100644 index 0000000000..972dedd3be --- /dev/null +++ b/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import DismissableBanner from 'flavours/glitch/components/dismissable_banner'; +import background from 'mastodon/../images/friends-cropped.png'; + + +export const ExplorePrompt = () => ( + + + +

+

+ +
+ + +
+
+); diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.jsx b/app/javascript/flavours/glitch/features/home_timeline/index.jsx index 791c310557..7036857020 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/home_timeline/index.jsx @@ -5,9 +5,10 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; -import { Link } from 'react-router-dom'; +import { List as ImmutableList } from 'immutable'; import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; @@ -19,6 +20,7 @@ import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_i import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container'; import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; +import { ExplorePrompt } from './components/explore_prompt'; import ColumnSettingsContainer from './containers/column_settings_container'; @@ -28,12 +30,36 @@ const messages = defineMessages({ hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' }, }); +const getHomeFeedSpeed = createSelector([ + state => state.getIn(['timelines', 'home', 'items'], ImmutableList()), + state => state.get('statuses'), +], (statusIds, statusMap) => { + const statuses = statusIds.take(20).map(id => statusMap.get(id)); + const uniqueAccountIds = (new Set(statuses.map(status => status.get('account')).toArray())).size; + const oldest = new Date(statuses.getIn([statuses.size - 1, 'created_at'], 0)); + const newest = new Date(statuses.getIn([0, 'created_at'], 0)); + const averageGap = (newest - oldest) / (1000 * (statuses.size + 1)); // Average gap between posts on first page in seconds + + return { + unique: uniqueAccountIds, + gap: averageGap, + newest, + }; +}); + +const homeTooSlow = createSelector(getHomeFeedSpeed, speed => + speed.unique < 5 // If there are fewer than 5 different accounts visible + || speed.gap > (30 * 60) // If the average gap between posts is more than 20 minutes + || (Date.now() - speed.newest) > (1000 * 3600) // If the most recent post is from over an hour ago +); + const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0, isPartial: state.getIn(['timelines', 'home', 'isPartial']), hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(), unreadAnnouncements: state.getIn(['announcements', 'items']).count(item => !item.get('read')), showAnnouncements: state.getIn(['announcements', 'show']), + tooSlow: homeTooSlow(state), regex: state.getIn(['settings', 'home', 'regex', 'body']), }); @@ -53,6 +79,7 @@ class HomeTimeline extends PureComponent { hasAnnouncements: PropTypes.bool, unreadAnnouncements: PropTypes.number, showAnnouncements: PropTypes.bool, + tooSlow: PropTypes.bool, regex: PropTypes.string, }; @@ -123,11 +150,11 @@ class HomeTimeline extends PureComponent { }; render () { - const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; + const { intl, hasUnread, columnId, multiColumn, tooSlow, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const pinned = !!columnId; const { signedIn } = this.context.identity; - let announcementsButton = null; + let announcementsButton, banner; if (hasAnnouncements) { announcementsButton = ( @@ -142,6 +169,10 @@ class HomeTimeline extends PureComponent { ); } + if (tooSlow) { + banner = ; + } + return ( }} />} + emptyMessage={} bindToDocument={!multiColumn} regex={this.props.regex} /> diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.jsx b/app/javascript/flavours/glitch/features/public_timeline/index.jsx index 5bbbea0662..4e4b350f8b 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/public_timeline/index.jsx @@ -146,11 +146,8 @@ class PublicTimeline extends PureComponent { - - - - } timelineId={`public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`} onLoadMore={this.handleLoadMore} trackScroll={!pinned} diff --git a/app/javascript/flavours/glitch/selectors/index.js b/app/javascript/flavours/glitch/selectors/index.js index e02bfab5db..a296ef8ede 100644 --- a/app/javascript/flavours/glitch/selectors/index.js +++ b/app/javascript/flavours/glitch/selectors/index.js @@ -137,3 +137,7 @@ export const getAccountHidden = createSelector([ ], (hidden, followingOrRequested, isSelf) => { return hidden && !(isSelf || followingOrRequested); }); + +export const getStatusList = createSelector([ + (state, type) => state.getIn(['status_lists', type, 'items']), +], (items) => items.toList()); diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index 533c5eda0f..97c8a84d69 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -960,26 +960,70 @@ $ui-header-height: 55px; } .dismissable-banner { - background: $ui-base-color; - border-bottom: 1px solid lighten($ui-base-color, 8%); - display: flex; - align-items: center; - gap: 30px; + position: relative; + margin: 10px; + margin-bottom: 5px; + border-radius: 8px; + border: 1px solid $highlight-text-color; + background: rgba($highlight-text-color, 0.15); + padding-inline-end: 45px; + overflow: hidden; + + &__background-image { + width: 125%; + position: absolute; + bottom: -25%; + inset-inline-end: -25%; + z-index: -1; + opacity: 0.15; + mix-blend-mode: luminosity; + } &__message { flex: 1 1 auto; - padding: 20px 15px; - cursor: default; - font-size: 14px; - line-height: 18px; + padding: 15px; + font-size: 15px; + line-height: 22px; + font-weight: 500; color: $primary-text-color; + + p { + margin-bottom: 15px; + + &:last-child { + margin-bottom: 0; + } + } + + h1 { + color: $highlight-text-color; + font-size: 22px; + line-height: 33px; + font-weight: 700; + margin-bottom: 15px; + } + + &__actions { + display: flex; + align-items: center; + gap: 4px; + margin-top: 30px; + } + + .button-tertiary { + background: rgba($ui-base-color, 0.15); + backdrop-filter: blur(8px); + } } &__action { - padding: 15px; - flex: 0 0 auto; - display: flex; - align-items: center; - justify-content: center; + position: absolute; + inset-inline-end: 0; + top: 0; + padding: 10px; + + .icon-button { + color: $highlight-text-color; + } } } diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss index 2294f2d7c2..cfcdd742e1 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss @@ -653,11 +653,6 @@ html { border: 1px solid lighten($ui-base-color, 8%); } -.dismissable-banner { - border-left: 1px solid lighten($ui-base-color, 8%); - border-right: 1px solid lighten($ui-base-color, 8%); -} - .status__content, .reply-indicator__content { a {