diff --git a/app/soapbox/features/ui/components/instance_info_panel.js b/app/soapbox/features/ui/components/instance_info_panel.js new file mode 100644 index 000000000..f27105ee9 --- /dev/null +++ b/app/soapbox/features/ui/components/instance_info_panel.js @@ -0,0 +1,191 @@ +'use strict'; + +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Icon from 'soapbox/components/icon'; +import { makeGetRemoteInstance } from 'soapbox/selectors'; + +const hasRestrictions = remoteInstance => { + return remoteInstance + .get('federation') + .deleteAll(['accept', 'reject_deletes', 'report_removal']) + .reduce((acc, value) => acc || value, false); +}; + +const getRemoteInstance = makeGetRemoteInstance(); + +const mapStateToProps = (state, { host }) => { + return { + instance: state.get('instance'), + remoteInstance: getRemoteInstance(state, host), + }; +}; + +export default @connect(mapStateToProps, null, null, { forwardRef: true }) +class InstanceInfoPanel extends ImmutablePureComponent { + + static propTypes = { + intl: PropTypes.object.isRequired, + host: PropTypes.string.isRequired, + instance: ImmutablePropTypes.map, + remoteInstance: ImmutablePropTypes.map, + }; + + renderRestrictions = () => { + const { remoteInstance } = this.props; + const items = []; + + const { + avatar_removal, + banner_removal, + federated_timeline_removal, + followers_only, + media_nsfw, + media_removal, + } = remoteInstance.get('federation').toJS(); + + const fullMediaRemoval = media_removal && avatar_removal && banner_removal; + const partialMediaRemoval = media_removal || avatar_removal || banner_removal; + + if (followers_only) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } else if (federated_timeline_removal) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } + + if (fullMediaRemoval) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } else if (partialMediaRemoval) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } + + if (!fullMediaRemoval && media_nsfw) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } + + return items; + } + + renderContent = () => { + const { host, instance, remoteInstance } = this.props; + if (!instance || !remoteInstance) return null; + + if (remoteInstance.getIn(['federation', 'reject']) === true) { + return ( +
+ + +
+ ); + } else if (hasRestrictions(remoteInstance)) { + return [ + ( +
+ +
+ ), + this.renderRestrictions(), + ]; + } else { + return ( +
+ + +
+ ); + } + } + + render() { + return ( +
+
+ + + + +
+
+ {this.renderContent()} +
+
+ ); + } + +} diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index fd522c07f..2837cd902 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -34,6 +34,7 @@ import HomePage from 'soapbox/pages/home_page'; import DefaultPage from 'soapbox/pages/default_page'; import EmptyPage from 'soapbox/pages/default_page'; import AdminPage from 'soapbox/pages/admin_page'; +import RemoteInstancePage from 'soapbox/pages/remote_instance_page'; import SidebarMenu from '../../components/sidebar_menu'; import { connectUserStream } from '../../actions/streaming'; import { Redirect } from 'react-router-dom'; @@ -194,7 +195,7 @@ class SwitchingColumnsArea extends React.PureComponent { - + {/* diff --git a/app/soapbox/pages/remote_instance_page.js b/app/soapbox/pages/remote_instance_page.js new file mode 100644 index 000000000..6c34940e0 --- /dev/null +++ b/app/soapbox/pages/remote_instance_page.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import WhoToFollowPanel from 'soapbox/features/ui/components/who_to_follow_panel'; +import TrendsPanel from 'soapbox/features/ui/components/trends_panel'; +import PromoPanel from 'soapbox/features/ui/components/promo_panel'; +import FeaturesPanel from 'soapbox/features/ui/components/features_panel'; +import LinkFooter from 'soapbox/features/ui/components/link_footer'; +import { getFeatures } from 'soapbox/utils/features'; +import InstanceInfoPanel from 'soapbox/features/ui/components/instance_info_panel'; + +const mapStateToProps = state => { + const features = getFeatures(state.get('instance')); + + return { + showTrendsPanel: features.trends, + showWhoToFollowPanel: features.suggestions, + }; +}; + +export default @connect(mapStateToProps) +class RemoteInstancePage extends ImmutablePureComponent { + + render() { + const { children, showTrendsPanel, showWhoToFollowPanel, params: { instance: host } } = this.props; + + return ( +
+
+
+ +
+
+ +
+
+ +
+
+ {children} +
+
+ +
+
+ {showTrendsPanel && } + {showWhoToFollowPanel && } + + + +
+
+
+
+
+ ); + } + +} diff --git a/app/soapbox/selectors/index.js b/app/soapbox/selectors/index.js index 9b222921e..b1bd570f7 100644 --- a/app/soapbox/selectors/index.js +++ b/app/soapbox/selectors/index.js @@ -1,5 +1,6 @@ import { createSelector } from 'reselect'; -import { List as ImmutableList } from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { getDomain } from 'soapbox/utils/accounts'; const getAccountBase = (state, id) => state.getIn(['accounts', id], null); const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null); @@ -215,3 +216,26 @@ export const makeGetOtherAccounts = () => { }, ImmutableList()); }); }; + +const getRemoteInstanceFavicon = (state, host) => ( + state.get('accounts') + .find(account => getDomain(account) === host, null, ImmutableMap()) + .getIn(['pleroma', 'favicon']) +); + +const getSimplePolicy = (state, host) => ( + state.getIn(['instance', 'pleroma', 'metadata', 'federation', 'mrf_simple'], ImmutableMap()) + .map(hosts => hosts.includes(host)) +); + +export const makeGetRemoteInstance = () => { + return createSelector([ + getRemoteInstanceFavicon, + getSimplePolicy, + ], (favicon, federation) => { + return ImmutableMap({ + favicon, + federation, + }); + }); +}; diff --git a/app/styles/application.scss b/app/styles/application.scss index 611eef9c4..f6611fdf0 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -82,6 +82,7 @@ @import 'components/backups'; @import 'components/crypto-donate'; @import 'components/datepicker'; +@import 'components/remote-timeline'; // Holiday @import 'holiday/halloween'; diff --git a/app/styles/components/remote-timeline.scss b/app/styles/components/remote-timeline.scss new file mode 100644 index 000000000..3fb00e731 --- /dev/null +++ b/app/styles/components/remote-timeline.scss @@ -0,0 +1,27 @@ +.instance-federation-panel { + &__message { + margin-bottom: 15px; + + i.fa { + padding-right: 10px; + } + } + + .wtf-panel__content { + box-sizing: border-box; + padding: 15px; + } +} + +.federation-restriction { + display: flex; + padding: 15px 0; + + &__icon { + width: 16px; + display: flex; + align-items: flex-start; + justify-content: center; + padding-right: 10px; + } +}