diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2022-10-09 06:08:37 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-09 06:08:37 +0200 |
commit | f41ec9af05d3e2145e62f705225dbabb7e04e242 (patch) | |
tree | 7d601a8ecac6ecbaf5bdd862ea1013fa816a57f6 | |
parent | a5112b51fdf3ec2b31690e064ea330a090e71957 (diff) |
Add dismissable hints to various timelines in web UI (#19315)
Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh>
8 files changed, 128 insertions, 11 deletions
diff --git a/app/javascript/mastodon/components/dismissable_banner.js b/app/javascript/mastodon/components/dismissable_banner.js new file mode 100644 index 000000000..1ee032056 --- /dev/null +++ b/app/javascript/mastodon/components/dismissable_banner.js @@ -0,0 +1,51 @@ +import React from 'react'; +import IconButton from './icon_button'; +import PropTypes from 'prop-types'; +import { injectIntl, defineMessages } from 'react-intl'; +import { bannerSettings } from 'mastodon/settings'; + +const messages = defineMessages({ + dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' }, +}); + +export default @injectIntl +class DismissableBanner extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + children: PropTypes.node, + intl: PropTypes.object.isRequired, + }; + + state = { + visible: !bannerSettings.get(this.props.id), + }; + + handleDismiss = () => { + const { id } = this.props; + this.setState({ visible: false }, () => bannerSettings.set(id, true)); + } + + render () { + const { visible } = this.state; + + if (!visible) { + return null; + } + + const { children, intl } = this.props; + + return ( + <div className='dismissable-banner'> + <div className='dismissable-banner__message'> + {children} + </div> + + <div className='dismissable-banner__action'> + <IconButton icon='times' title={intl.formatMessage(messages.dismiss)} onClick={this.handleDismiss} /> + </div> + </div> + ); + } + +} diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index afa7b3ed4..757521802 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -10,6 +10,8 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import ColumnSettingsContainer from './containers/column_settings_container'; import { connectCommunityStream } from '../../actions/streaming'; import { Helmet } from 'react-helmet'; +import { domain } from 'mastodon/initial_state'; +import DismissableBanner from 'mastodon/components/dismissable_banner'; const messages = defineMessages({ title: { id: 'column.community', defaultMessage: 'Local timeline' }, @@ -134,6 +136,10 @@ class CommunityTimeline extends React.PureComponent { <ColumnSettingsContainer columnId={columnId} /> </ColumnHeader> + <DismissableBanner id='community_timeline'> + <FormattedMessage id='dismissable_banner.community_timeline' defaultMessage='These are the most recent public posts from people whose accounts are hosted by {domain}.' values={{ domain }} /> + </DismissableBanner> + <StatusListContainer trackScroll={!pinned} scrollKey={`community_timeline-${columnId}`} diff --git a/app/javascript/mastodon/features/explore/links.js b/app/javascript/mastodon/features/explore/links.js index d3aaa9cdd..b47fc8fcf 100644 --- a/app/javascript/mastodon/features/explore/links.js +++ b/app/javascript/mastodon/features/explore/links.js @@ -6,6 +6,7 @@ import LoadingIndicator from 'mastodon/components/loading_indicator'; import { connect } from 'react-redux'; import { fetchTrendingLinks } from 'mastodon/actions/trends'; import { FormattedMessage } from 'react-intl'; +import DismissableBanner from 'mastodon/components/dismissable_banner'; const mapStateToProps = state => ({ links: state.getIn(['trends', 'links', 'items']), @@ -29,9 +30,17 @@ class Links extends React.PureComponent { render () { const { isLoading, links } = this.props; + const banner = ( + <DismissableBanner id='explore/links'> + <FormattedMessage id='dismissable_banner.explore_links' defaultMessage='These news stories are being talked about by people on this and other servers of the decentralized network right now.' /> + </DismissableBanner> + ); + if (!isLoading && links.isEmpty()) { return ( <div className='explore__links scrollable scrollable--flex'> + {banner} + <div className='empty-column-indicator'> <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' /> </div> @@ -41,6 +50,8 @@ class Links extends React.PureComponent { return ( <div className='explore__links'> + {banner} + {isLoading ? (<LoadingIndicator />) : links.map(link => ( <Story key={link.get('id')} diff --git a/app/javascript/mastodon/features/explore/statuses.js b/app/javascript/mastodon/features/explore/statuses.js index 33e5b4179..791f11b9f 100644 --- a/app/javascript/mastodon/features/explore/statuses.js +++ b/app/javascript/mastodon/features/explore/statuses.js @@ -6,6 +6,7 @@ import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { fetchTrendingStatuses, expandTrendingStatuses } from 'mastodon/actions/trends'; import { debounce } from 'lodash'; +import DismissableBanner from 'mastodon/components/dismissable_banner'; const mapStateToProps = state => ({ statusIds: state.getIn(['status_lists', 'trending', 'items']), @@ -40,17 +41,23 @@ class Statuses extends React.PureComponent { const emptyMessage = <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />; return ( - <StatusList - trackScroll - statusIds={statusIds} - scrollKey='explore-statuses' - hasMore={hasMore} - isLoading={isLoading} - onLoadMore={this.handleLoadMore} - emptyMessage={emptyMessage} - bindToDocument={!multiColumn} - withCounters - /> + <> + <DismissableBanner id='explore/statuses'> + <FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These posts from this and other servers in the decentralized network are gaining traction on this server right now.' /> + </DismissableBanner> + + <StatusList + trackScroll + statusIds={statusIds} + scrollKey='explore-statuses' + hasMore={hasMore} + isLoading={isLoading} + onLoadMore={this.handleLoadMore} + emptyMessage={emptyMessage} + bindToDocument={!multiColumn} + withCounters + /> + </> ); } diff --git a/app/javascript/mastodon/features/explore/tags.js b/app/javascript/mastodon/features/explore/tags.js index 6cd3a6fb1..258dc392f 100644 --- a/app/javascript/mastodon/features/explore/tags.js +++ b/app/javascript/mastodon/features/explore/tags.js @@ -6,6 +6,7 @@ import LoadingIndicator from 'mastodon/components/loading_indicator'; import { connect } from 'react-redux'; import { fetchTrendingHashtags } from 'mastodon/actions/trends'; import { FormattedMessage } from 'react-intl'; +import DismissableBanner from 'mastodon/components/dismissable_banner'; const mapStateToProps = state => ({ hashtags: state.getIn(['trends', 'tags', 'items']), @@ -29,9 +30,17 @@ class Tags extends React.PureComponent { render () { const { isLoading, hashtags } = this.props; + const banner = ( + <DismissableBanner id='explore/tags'> + <FormattedMessage id='dismissable_banner.explore_tags' defaultMessage='These hashtags are gaining traction among people on this and other servers of the decentralized network right now.' /> + </DismissableBanner> + ); + if (!isLoading && hashtags.isEmpty()) { return ( <div className='explore__links scrollable scrollable--flex'> + {banner} + <div className='empty-column-indicator'> <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' /> </div> @@ -41,6 +50,8 @@ class Tags extends React.PureComponent { return ( <div className='explore__links'> + {banner} + {isLoading ? (<LoadingIndicator />) : hashtags.map(hashtag => ( <Hashtag key={hashtag.get('name')} hashtag={hashtag} /> ))} diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index 5b1b7c650..8dbef98c0 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -10,6 +10,7 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import ColumnSettingsContainer from './containers/column_settings_container'; import { connectPublicStream } from '../../actions/streaming'; import { Helmet } from 'react-helmet'; +import DismissableBanner from 'mastodon/components/dismissable_banner'; const messages = defineMessages({ title: { id: 'column.public', defaultMessage: 'Federated timeline' }, @@ -137,6 +138,10 @@ class PublicTimeline extends React.PureComponent { <ColumnSettingsContainer columnId={columnId} /> </ColumnHeader> + <DismissableBanner id='public_timeline'> + <FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.' /> + </DismissableBanner> + <StatusListContainer timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`} onLoadMore={this.handleLoadMore} diff --git a/app/javascript/mastodon/settings.js b/app/javascript/mastodon/settings.js index 7643a508e..46cfadfa3 100644 --- a/app/javascript/mastodon/settings.js +++ b/app/javascript/mastodon/settings.js @@ -45,3 +45,4 @@ export default class Settings { export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); export const tagHistory = new Settings('mastodon_tag_history'); +export const bannerSettings = new Settings('mastodon_banner_settings'); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index cc8455ce3..02c2a14bf 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -8312,3 +8312,28 @@ noscript { } } } + +.dismissable-banner { + background: $ui-base-color; + border-bottom: 1px solid lighten($ui-base-color, 8%); + display: flex; + align-items: center; + gap: 30px; + + &__message { + flex: 1 1 auto; + padding: 20px 15px; + cursor: default; + font-size: 14px; + line-height: 18px; + color: $primary-text-color; + } + + &__action { + padding: 15px; + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + } +} |