From 570686b83abc35fd7383b6fb27c434337394dce8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 9 May 2022 21:35:22 -0500 Subject: [PATCH 1/7] RemoteInstancePage: convert to TSX --- app/soapbox/pages/remote_instance_page.js | 57 ---------------------- app/soapbox/pages/remote_instance_page.tsx | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 57 deletions(-) delete mode 100644 app/soapbox/pages/remote_instance_page.js create mode 100644 app/soapbox/pages/remote_instance_page.tsx diff --git a/app/soapbox/pages/remote_instance_page.js b/app/soapbox/pages/remote_instance_page.js deleted file mode 100644 index 187eab26a..000000000 --- a/app/soapbox/pages/remote_instance_page.js +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - -import LinkFooter from 'soapbox/features/ui/components/link_footer'; -import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; -import { - PromoPanel, - InstanceInfoPanel, - InstanceModerationPanel, -} from 'soapbox/features/ui/util/async-components'; -import { federationRestrictionsDisclosed } from 'soapbox/utils/state'; - -import { Layout } from '../components/ui'; - -const mapStateToProps = state => { - const me = state.me; - const account = state.accounts.get(me); - - return { - me, - disclosed: federationRestrictionsDisclosed(state), - isAdmin: Boolean(account?.admin), - }; -}; - -export default @connect(mapStateToProps) -class RemoteInstancePage extends ImmutablePureComponent { - - render() { - const { children, params: { instance: host }, disclosed, isAdmin } = this.props; - - return ( - <> - - {children} - - - - - {Component => } - - - {Component => } - - {(disclosed || isAdmin) && ( - - {Component => } - - )} - - - - ); - } - -} diff --git a/app/soapbox/pages/remote_instance_page.tsx b/app/soapbox/pages/remote_instance_page.tsx new file mode 100644 index 000000000..548e0f9de --- /dev/null +++ b/app/soapbox/pages/remote_instance_page.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import LinkFooter from 'soapbox/features/ui/components/link_footer'; +import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; +import { + PromoPanel, + InstanceInfoPanel, + InstanceModerationPanel, +} from 'soapbox/features/ui/util/async-components'; +import { useAppSelector, useOwnAccount } from 'soapbox/hooks'; +import { federationRestrictionsDisclosed } from 'soapbox/utils/state'; + +import { Layout } from '../components/ui'; + +interface IRemoteInstancePage { + params: { + instance: string, + }, +} + +/** Page for viewing a remote instance timeline. */ +const RemoteInstancePage: React.FC = ({ children, params: { instance: host } }) => { + const account = useOwnAccount(); + const disclosed = useAppSelector(federationRestrictionsDisclosed); + + return ( + <> + + {children} + + + + + {Component => } + + + {Component => } + + {(disclosed || account?.admin) && ( + + {Component => } + + )} + + + + ); +}; + +export default RemoteInstancePage; From 57c803037430631bf5ce44f4a3f396f1595b9480 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 9 May 2022 21:49:53 -0500 Subject: [PATCH 2/7] InstanceInfoPanel: convert to TSX --- .../ui/components/instance_info_panel.js | 84 ------------------- .../ui/components/instance_info_panel.tsx | 68 +++++++++++++++ app/soapbox/pages/remote_instance_page.tsx | 8 +- 3 files changed, 73 insertions(+), 87 deletions(-) delete mode 100644 app/soapbox/features/ui/components/instance_info_panel.js create mode 100644 app/soapbox/features/ui/components/instance_info_panel.tsx diff --git a/app/soapbox/features/ui/components/instance_info_panel.js b/app/soapbox/features/ui/components/instance_info_panel.js deleted file mode 100644 index 20582ddcf..000000000 --- a/app/soapbox/features/ui/components/instance_info_panel.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; - -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { injectIntl, defineMessages } from 'react-intl'; -import { connect } from 'react-redux'; - -import { pinHost, unpinHost } from 'soapbox/actions/remote_timeline'; -import { getSettings } from 'soapbox/actions/settings'; -import DropdownMenu from 'soapbox/containers/dropdown_menu_container'; -import { makeGetRemoteInstance } from 'soapbox/selectors'; - -const getRemoteInstance = makeGetRemoteInstance(); - -const messages = defineMessages({ - pinHost: { id: 'remote_instance.pin_host', defaultMessage: 'Pin {host}' }, - unpinHost: { id: 'remote_instance.unpin_host', defaultMessage: 'Unpin {host}' }, -}); - -const mapStateToProps = (state, { host }) => { - const settings = getSettings(state); - - return { - instance: state.get('instance'), - remoteInstance: getRemoteInstance(state, host), - pinned: settings.getIn(['remote_timeline', 'pinnedHosts']).includes(host), - }; -}; - -export default @connect(mapStateToProps, null, null, { forwardRef: true }) -@injectIntl -class InstanceInfoPanel extends ImmutablePureComponent { - - static propTypes = { - intl: PropTypes.object.isRequired, - host: PropTypes.string.isRequired, - instance: ImmutablePropTypes.map, - remoteInstance: ImmutablePropTypes.map, - pinned: PropTypes.bool, - }; - - handlePinHost = e => { - const { dispatch, host, pinned } = this.props; - - if (!pinned) { - dispatch(pinHost(host)); - } else { - dispatch(unpinHost(host)); - } - } - - makeMenu = () => { - const { intl, host, pinned } = this.props; - - return [{ - text: intl.formatMessage(pinned ? messages.unpinHost : messages.pinHost, { host }), - action: this.handlePinHost, - icon: require(pinned ? '@tabler/icons/icons/pinned-off.svg' : '@tabler/icons/icons/pin.svg'), - }]; - } - - render() { - const { remoteInstance, pinned } = this.props; - const menu = this.makeMenu(); - const icon = pinned ? 'thumbtack' : 'globe-w'; - - return ( -
-
- - - {remoteInstance.get('host')} - -
- -
-
-
- ); - } - -} diff --git a/app/soapbox/features/ui/components/instance_info_panel.tsx b/app/soapbox/features/ui/components/instance_info_panel.tsx new file mode 100644 index 000000000..a100abc2c --- /dev/null +++ b/app/soapbox/features/ui/components/instance_info_panel.tsx @@ -0,0 +1,68 @@ +'use strict'; + +import React from 'react'; +import { useIntl, defineMessages } from 'react-intl'; + +import { pinHost, unpinHost } from 'soapbox/actions/remote_timeline'; +import DropdownMenu from 'soapbox/containers/dropdown_menu_container'; +import { useAppSelector, useAppDispatch, useSettings } from 'soapbox/hooks'; +import { makeGetRemoteInstance } from 'soapbox/selectors'; + +const getRemoteInstance = makeGetRemoteInstance(); + +const messages = defineMessages({ + pinHost: { id: 'remote_instance.pin_host', defaultMessage: 'Pin {host}' }, + unpinHost: { id: 'remote_instance.unpin_host', defaultMessage: 'Unpin {host}' }, +}); + +interface IInstanceInfoPanel { + /** Hostname (domain) of the remote instance, eg "gleasonator.com" */ + host: string, +} + +/** Widget that displays information about a remote instance to users. */ +const InstanceInfoPanel: React.FC = ({ host }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const settings = useSettings(); + const remoteInstance: any = useAppSelector(state => getRemoteInstance(state, host)); + const pinned: boolean = (settings.getIn(['remote_timeline', 'pinnedHosts']) as any).includes(host); + + const handlePinHost: React.MouseEventHandler = () => { + if (!pinned) { + dispatch(pinHost(host)); + } else { + dispatch(unpinHost(host)); + } + }; + + const makeMenu = () => { + return [{ + text: intl.formatMessage(pinned ? messages.unpinHost : messages.pinHost, { host }), + action: handlePinHost, + icon: require(pinned ? '@tabler/icons/icons/pinned-off.svg' : '@tabler/icons/icons/pin.svg'), + }]; + }; + + const menu = makeMenu(); + const icon = pinned ? 'thumbtack' : 'globe-w'; + + if (!remoteInstance) return null; + + return ( +
+
+ + + {remoteInstance.get('host')} + +
+ +
+
+
+ ); +}; + +export default InstanceInfoPanel; diff --git a/app/soapbox/pages/remote_instance_page.tsx b/app/soapbox/pages/remote_instance_page.tsx index 548e0f9de..e3488908c 100644 --- a/app/soapbox/pages/remote_instance_page.tsx +++ b/app/soapbox/pages/remote_instance_page.tsx @@ -13,13 +13,15 @@ import { federationRestrictionsDisclosed } from 'soapbox/utils/state'; import { Layout } from '../components/ui'; interface IRemoteInstancePage { - params: { - instance: string, + params?: { + instance?: string, }, } /** Page for viewing a remote instance timeline. */ -const RemoteInstancePage: React.FC = ({ children, params: { instance: host } }) => { +const RemoteInstancePage: React.FC = ({ children, params }) => { + const host = params?.instance; + const account = useOwnAccount(); const disclosed = useAppSelector(federationRestrictionsDisclosed); From 1991e5751da8a234d836b29b6d09817013726afd Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 9 May 2022 21:59:30 -0500 Subject: [PATCH 3/7] PinnedHostsPicker: convert to TSX --- .../components/pinned_hosts_picker.js | 46 ------------------- .../components/pinned_hosts_picker.tsx | 31 +++++++++++++ 2 files changed, 31 insertions(+), 46 deletions(-) delete mode 100644 app/soapbox/features/remote_timeline/components/pinned_hosts_picker.js create mode 100644 app/soapbox/features/remote_timeline/components/pinned_hosts_picker.tsx diff --git a/app/soapbox/features/remote_timeline/components/pinned_hosts_picker.js b/app/soapbox/features/remote_timeline/components/pinned_hosts_picker.js deleted file mode 100644 index f9cc609a2..000000000 --- a/app/soapbox/features/remote_timeline/components/pinned_hosts_picker.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; - -import { getSettings } from 'soapbox/actions/settings'; - -const mapStateToProps = state => { - const settings = getSettings(state); - - return { - pinnedHosts: settings.getIn(['remote_timeline', 'pinnedHosts']), - }; -}; - -class PinnedHostsPicker extends React.PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - pinnedHosts: ImmutablePropTypes.orderedSet, - host: PropTypes.string, - }; - - render() { - const { pinnedHosts, host: activeHost } = this.props; - - if (!pinnedHosts || pinnedHosts.isEmpty()) return null; - - return ( -
- {pinnedHosts.map(host => ( -
- {host} -
- ))} -
- ); - } - -} - -export default connect(mapStateToProps)(PinnedHostsPicker); diff --git a/app/soapbox/features/remote_timeline/components/pinned_hosts_picker.tsx b/app/soapbox/features/remote_timeline/components/pinned_hosts_picker.tsx new file mode 100644 index 000000000..f1c1ac425 --- /dev/null +++ b/app/soapbox/features/remote_timeline/components/pinned_hosts_picker.tsx @@ -0,0 +1,31 @@ +'use strict'; + +import classNames from 'classnames'; +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { useSettings } from 'soapbox/hooks'; + +interface IPinnedHostsPicker { + /** The active host among pinned hosts. */ + host: string, +} + +const PinnedHostsPicker: React.FC = ({ host: activeHost }) => { + const settings = useSettings(); + const pinnedHosts = settings.getIn(['remote_timeline', 'pinnedHosts']) as any; + + if (!pinnedHosts || pinnedHosts.isEmpty()) return null; + + return ( +
+ {pinnedHosts.map((host: any) => ( +
+ {host} +
+ ))} +
+ ); +}; + +export default PinnedHostsPicker; From 035c9741375f154efe989c5a441a0fa725c7aad3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 9 May 2022 22:22:01 -0500 Subject: [PATCH 4/7] RemoteTimeline: convert to TSX --- app/soapbox/features/remote_timeline/index.js | 116 ------------------ .../features/remote_timeline/index.tsx | 94 ++++++++++++++ 2 files changed, 94 insertions(+), 116 deletions(-) delete mode 100644 app/soapbox/features/remote_timeline/index.js create mode 100644 app/soapbox/features/remote_timeline/index.tsx diff --git a/app/soapbox/features/remote_timeline/index.js b/app/soapbox/features/remote_timeline/index.js deleted file mode 100644 index a207498c6..000000000 --- a/app/soapbox/features/remote_timeline/index.js +++ /dev/null @@ -1,116 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; -import { withRouter } from 'react-router-dom'; - -import { getSettings } from 'soapbox/actions/settings'; -import IconButton from 'soapbox/components/icon_button'; -import Column from 'soapbox/features/ui/components/column'; - -import { connectRemoteStream } from '../../actions/streaming'; -import { expandRemoteTimeline } from '../../actions/timelines'; -import StatusListContainer from '../ui/containers/status_list_container'; - -import PinnedHostsPicker from './components/pinned_hosts_picker'; - -const messages = defineMessages({ - title: { id: 'column.remote', defaultMessage: 'Federated timeline' }, -}); - -const mapStateToProps = (state, props) => { - const instance = props.params.instance; - const settings = getSettings(state); - const onlyMedia = settings.getIn(['remote', 'other', 'onlyMedia']); - - const timelineId = 'remote'; - - return { - timelineId, - onlyMedia, - hasUnread: state.getIn(['timelines', `${timelineId}${onlyMedia ? ':media' : ''}:${instance}`, 'unread']) > 0, - instance, - pinned: settings.getIn(['remote_timeline', 'pinnedHosts']).includes(instance), - }; -}; - -export default @connect(mapStateToProps) -@injectIntl -@withRouter -class RemoteTimeline extends React.PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - hasUnread: PropTypes.bool, - onlyMedia: PropTypes.bool, - timelineId: PropTypes.string, - instance: PropTypes.string.isRequired, - pinned: PropTypes.bool, - history: PropTypes.object, - }; - - componentDidMount() { - const { dispatch, onlyMedia, instance } = this.props; - dispatch(expandRemoteTimeline(instance, { onlyMedia })); - this.disconnect = dispatch(connectRemoteStream(instance, { onlyMedia })); - } - - componentDidUpdate(prevProps) { - if (prevProps.onlyMedia !== this.props.onlyMedia) { - const { dispatch, onlyMedia, instance } = this.props; - this.disconnect(); - - dispatch(expandRemoteTimeline(instance, { onlyMedia })); - this.disconnect = dispatch(connectRemoteStream(instance, { onlyMedia })); - } - } - - componentWillUnmount() { - if (this.disconnect) { - this.disconnect(); - this.disconnect = null; - } - } - - handleCloseClick = e => { - this.props.history.push('/timeline/fediverse'); - } - - handleLoadMore = maxId => { - const { dispatch, onlyMedia, instance } = this.props; - dispatch(expandRemoteTimeline(instance, { maxId, onlyMedia })); - } - - render() { - const { intl, onlyMedia, timelineId, instance, pinned } = this.props; - - return ( - - - {!pinned &&
- - -
} - - } - divideType='space' - /> -
- ); - } - -} diff --git a/app/soapbox/features/remote_timeline/index.tsx b/app/soapbox/features/remote_timeline/index.tsx new file mode 100644 index 000000000..d3560f4a6 --- /dev/null +++ b/app/soapbox/features/remote_timeline/index.tsx @@ -0,0 +1,94 @@ +import React, { useEffect, useRef } from 'react'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; +import { useHistory } from 'react-router-dom'; + +import IconButton from 'soapbox/components/icon_button'; +import Column from 'soapbox/features/ui/components/column'; +import { useAppDispatch, useSettings } from 'soapbox/hooks'; + +import { connectRemoteStream } from '../../actions/streaming'; +import { expandRemoteTimeline } from '../../actions/timelines'; +import StatusListContainer from '../ui/containers/status_list_container'; + +import PinnedHostsPicker from './components/pinned_hosts_picker'; + +const messages = defineMessages({ + title: { id: 'column.remote', defaultMessage: 'Federated timeline' }, +}); + +interface IRemoteTimeline { + params?: { + instance?: string, + } +} + +/** View statuses from a remote instance. */ +const RemoteTimeline: React.FC = ({ params }) => { + const intl = useIntl(); + const history = useHistory(); + const dispatch = useAppDispatch(); + + const instance = params?.instance; + const settings = useSettings(); + + const stream = useRef(null); + + const timelineId = 'remote'; + const onlyMedia = !!settings.getIn(['remote', 'other', 'onlyMedia']); + + const pinned: boolean = (settings.getIn(['remote_timeline', 'pinnedHosts']) as any).includes(instance); + + const disconnect = () => { + if (stream.current) { + stream.current(); + } + }; + + useEffect(() => { + disconnect(); + dispatch(expandRemoteTimeline(instance, { onlyMedia, maxId: undefined })); + stream.current = dispatch(connectRemoteStream(instance, { onlyMedia })); + + return () => { + disconnect(); + stream.current = null; + }; + }, [onlyMedia]); + + const handleCloseClick: React.MouseEventHandler = () => { + history.push('/timeline/fediverse'); + }; + + const handleLoadMore = (maxId: string) => { + dispatch(expandRemoteTimeline(instance, { maxId, onlyMedia })); + }; + + return ( + + {instance && } + {!pinned &&
+ + +
} + + } + divideType='space' + /> +
+ ); +}; + +export default RemoteTimeline; From 5a23d75aa518bccbb692c7db7fd393d0c283ead0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 9 May 2022 22:27:39 -0500 Subject: [PATCH 5/7] InstanceModerationPanel: convert to tsx --- .../components/instance_moderation_panel.js | 81 ------------------- .../components/instance_moderation_panel.tsx | 65 +++++++++++++++ 2 files changed, 65 insertions(+), 81 deletions(-) delete mode 100644 app/soapbox/features/ui/components/instance_moderation_panel.js create mode 100644 app/soapbox/features/ui/components/instance_moderation_panel.tsx diff --git a/app/soapbox/features/ui/components/instance_moderation_panel.js b/app/soapbox/features/ui/components/instance_moderation_panel.js deleted file mode 100644 index 2f18ba887..000000000 --- a/app/soapbox/features/ui/components/instance_moderation_panel.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict'; - -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; - -import { openModal } from 'soapbox/actions/modals'; -import DropdownMenu from 'soapbox/containers/dropdown_menu_container'; -import InstanceRestrictions from 'soapbox/features/federation_restrictions/components/instance_restrictions'; -import { makeGetRemoteInstance } from 'soapbox/selectors'; - -const getRemoteInstance = makeGetRemoteInstance(); - -const messages = defineMessages({ - editFederation: { id: 'remote_instance.edit_federation', defaultMessage: 'Edit federation' }, -}); - -const mapStateToProps = (state, { host }) => { - const { me, instance } = state; - const account = state.accounts.get(me); - - return { - instance, - remoteInstance: getRemoteInstance(state, host), - isAdmin: account.admin, - }; -}; - -export default @connect(mapStateToProps, null, null, { forwardRef: true }) -@injectIntl -class InstanceModerationPanel extends ImmutablePureComponent { - - static propTypes = { - intl: PropTypes.object.isRequired, - host: PropTypes.string.isRequired, - instance: ImmutablePropTypes.map, - remoteInstance: ImmutablePropTypes.map, - isAdmin: PropTypes.bool, - }; - - handleEditFederation = e => { - const { dispatch, host } = this.props; - dispatch(openModal('EDIT_FEDERATION', { host })); - } - - makeMenu = () => { - const { intl } = this.props; - - return [{ - text: intl.formatMessage(messages.editFederation), - action: this.handleEditFederation, - icon: require('@tabler/icons/icons/edit.svg'), - }]; - } - - render() { - const { remoteInstance, isAdmin } = this.props; - const menu = this.makeMenu(); - - return ( -
-
- - - - - {isAdmin &&
- -
} -
-
- -
-
- ); - } - -} diff --git a/app/soapbox/features/ui/components/instance_moderation_panel.tsx b/app/soapbox/features/ui/components/instance_moderation_panel.tsx new file mode 100644 index 000000000..1a494df7e --- /dev/null +++ b/app/soapbox/features/ui/components/instance_moderation_panel.tsx @@ -0,0 +1,65 @@ +'use strict'; + +import React from 'react'; +import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; + +import { openModal } from 'soapbox/actions/modals'; +import DropdownMenu from 'soapbox/containers/dropdown_menu_container'; +import InstanceRestrictions from 'soapbox/features/federation_restrictions/components/instance_restrictions'; +import { useAppSelector, useAppDispatch, useOwnAccount } from 'soapbox/hooks'; +import { makeGetRemoteInstance } from 'soapbox/selectors'; + +const getRemoteInstance = makeGetRemoteInstance(); + +const messages = defineMessages({ + editFederation: { id: 'remote_instance.edit_federation', defaultMessage: 'Edit federation' }, +}); + +interface IInstanceModerationPanel { + /** Host (eg "gleasonator.com") of the remote instance to moderate. */ + host: string, +} + +/** Widget for moderators to manage a remote instance. */ +const InstanceModerationPanel: React.FC = ({ host }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const account = useOwnAccount(); + const remoteInstance = useAppSelector(state => getRemoteInstance(state, host)); + + const handleEditFederation = () => { + dispatch(openModal('EDIT_FEDERATION', { host })); + }; + + const makeMenu = () => { + return [{ + text: intl.formatMessage(messages.editFederation), + action: handleEditFederation, + icon: require('@tabler/icons/icons/edit.svg'), + }]; + }; + + const menu = makeMenu(); + + return ( +
+
+ + + + + {account?.admin && ( +
+ +
+ )} +
+
+ +
+
+ ); +}; + +export default InstanceModerationPanel; From 57cfd9b18e0c7e3977e5ef9c32f9fff6c0cb64e5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 9 May 2022 22:39:57 -0500 Subject: [PATCH 6/7] EditFederationModal: convert to tsx --- app/soapbox/features/forms/index.tsx | 1 + .../ui/components/edit_federation_modal.js | 151 ------------------ .../ui/components/edit_federation_modal.tsx | 122 ++++++++++++++ 3 files changed, 123 insertions(+), 151 deletions(-) delete mode 100644 app/soapbox/features/ui/components/edit_federation_modal.js create mode 100644 app/soapbox/features/ui/components/edit_federation_modal.tsx diff --git a/app/soapbox/features/forms/index.tsx b/app/soapbox/features/forms/index.tsx index c458cd6b1..9504d0560 100644 --- a/app/soapbox/features/forms/index.tsx +++ b/app/soapbox/features/forms/index.tsx @@ -167,6 +167,7 @@ interface ICheckbox { hint?: React.ReactNode, name?: string, checked?: boolean, + disabled?: boolean, onChange?: React.ChangeEventHandler, required?: boolean, } diff --git a/app/soapbox/features/ui/components/edit_federation_modal.js b/app/soapbox/features/ui/components/edit_federation_modal.js deleted file mode 100644 index 53e21078f..000000000 --- a/app/soapbox/features/ui/components/edit_federation_modal.js +++ /dev/null @@ -1,151 +0,0 @@ -import { Map as ImmutableMap, is } from 'immutable'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; - -import { updateMrf } from 'soapbox/actions/mrf'; -import snackbar from 'soapbox/actions/snackbar'; -import { SimpleForm, Checkbox } from 'soapbox/features/forms'; -import { makeGetRemoteInstance } from 'soapbox/selectors'; - -const getRemoteInstance = makeGetRemoteInstance(); - -const messages = defineMessages({ - reject: { id: 'edit_federation.reject', defaultMessage: 'Reject all activities' }, - mediaRemoval: { id: 'edit_federation.media_removal', defaultMessage: 'Strip media' }, - forceNsfw: { id: 'edit_federation.force_nsfw', defaultMessage: 'Force attachments to be marked sensitive' }, - unlisted: { id: 'edit_federation.unlisted', defaultMessage: 'Force posts unlisted' }, - followersOnly: { id: 'edit_federation.followers_only', defaultMessage: 'Hide posts except to followers' }, - save: { id: 'edit_federation.save', defaultMessage: 'Save' }, - success: { id: 'edit_federation.success', defaultMessage: '{host} federation was updated' }, -}); - -const mapStateToProps = (state, { host }) => { - return { - remoteInstance: getRemoteInstance(state, host), - }; -}; - -export default @connect(mapStateToProps) -@injectIntl -class EditFederationModal extends ImmutablePureComponent { - - static propTypes = { - host: PropTypes.string.isRequired, - remoteInstance: ImmutablePropTypes.map, - }; - - state = { - data: ImmutableMap(), - } - - hydrateState = () => { - const { remoteInstance } = this.props; - this.setState({ data: remoteInstance.get('federation') }); - } - - componentDidMount() { - this.hydrateState(); - } - - componentDidUpdate(prevProps) { - const { remoteInstance } = this.props; - - if (!is(prevProps.remoteInstance, remoteInstance)) { - this.hydrateState(); - } - } - - handleDataChange = key => { - return ({ target }) => { - const { data } = this.state; - this.setState({ data: data.set(key, target.checked) }); - }; - } - - handleMediaRemoval = ({ target: { checked } }) => { - const data = this.state.data.merge({ - avatar_removal: checked, - banner_removal: checked, - media_removal: checked, - }); - - this.setState({ data }); - } - - handleSubmit = e => { - const { intl, dispatch, host, onClose } = this.props; - const { data } = this.state; - - dispatch(updateMrf(host, data)) - .then(() => dispatch(snackbar.success(intl.formatMessage(messages.success, { host })))) - .catch(() => {}); - - onClose(); - } - - render() { - const { intl, remoteInstance } = this.props; - const { data } = this.state; - - const { - avatar_removal, - banner_removal, - federated_timeline_removal, - followers_only, - media_nsfw, - media_removal, - reject, - } = data.toJS(); - - const fullMediaRemoval = avatar_removal && banner_removal && media_removal; - - return ( -
-
-
- {remoteInstance.get('host')} -
- - - - - - - - -
-
- ); - } - -} diff --git a/app/soapbox/features/ui/components/edit_federation_modal.tsx b/app/soapbox/features/ui/components/edit_federation_modal.tsx new file mode 100644 index 000000000..3d4456a75 --- /dev/null +++ b/app/soapbox/features/ui/components/edit_federation_modal.tsx @@ -0,0 +1,122 @@ +import { Map as ImmutableMap } from 'immutable'; +import React, { useState, useEffect } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; + +import { updateMrf } from 'soapbox/actions/mrf'; +import snackbar from 'soapbox/actions/snackbar'; +import { SimpleForm, Checkbox } from 'soapbox/features/forms'; +import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; +import { makeGetRemoteInstance } from 'soapbox/selectors'; + +const getRemoteInstance = makeGetRemoteInstance(); + +const messages = defineMessages({ + reject: { id: 'edit_federation.reject', defaultMessage: 'Reject all activities' }, + mediaRemoval: { id: 'edit_federation.media_removal', defaultMessage: 'Strip media' }, + forceNsfw: { id: 'edit_federation.force_nsfw', defaultMessage: 'Force attachments to be marked sensitive' }, + unlisted: { id: 'edit_federation.unlisted', defaultMessage: 'Force posts unlisted' }, + followersOnly: { id: 'edit_federation.followers_only', defaultMessage: 'Hide posts except to followers' }, + save: { id: 'edit_federation.save', defaultMessage: 'Save' }, + success: { id: 'edit_federation.success', defaultMessage: '{host} federation was updated' }, +}); + +interface IEditFederationModal { + host: string, + onClose: () => void, +} + +/** Modal for moderators to edit federation with a remote instance. */ +const EditFederationModal: React.FC = ({ host, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const remoteInstance = useAppSelector(state => getRemoteInstance(state, host)); + + const [data, setData] = useState(ImmutableMap()); + + useEffect(() => { + setData(remoteInstance.get('federation') as any); + }, [remoteInstance]); + + const handleDataChange = (key: string): React.ChangeEventHandler => { + return ({ target }) => { + setData(data.set(key, target.checked)); + }; + }; + + const handleMediaRemoval: React.ChangeEventHandler = ({ target: { checked } }) => { + const newData = data.merge({ + avatar_removal: checked, + banner_removal: checked, + media_removal: checked, + }); + + setData(newData); + }; + + const handleSubmit: React.FormEventHandler = () => { + dispatch(updateMrf(host, data)) + .then(() => dispatch(snackbar.success(intl.formatMessage(messages.success, { host })))) + .catch(() => {}); + + onClose(); + }; + + const { + avatar_removal, + banner_removal, + federated_timeline_removal, + followers_only, + media_nsfw, + media_removal, + reject, + } = data.toJS() as Record; + + const fullMediaRemoval = avatar_removal && banner_removal && media_removal; + + return ( +
+
+
+ {host} +
+ + + + + + + + +
+
+ ); +}; + +export default EditFederationModal; From c66790a39e4d266ae3bee3542e8fe6ba4ae95dcd Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 11 May 2022 12:22:10 -0500 Subject: [PATCH 7/7] RemoteTimeline: reorganize useEffect --- app/soapbox/features/remote_timeline/index.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/soapbox/features/remote_timeline/index.tsx b/app/soapbox/features/remote_timeline/index.tsx index d3560f4a6..c429549f5 100644 --- a/app/soapbox/features/remote_timeline/index.tsx +++ b/app/soapbox/features/remote_timeline/index.tsx @@ -44,6 +44,14 @@ const RemoteTimeline: React.FC = ({ params }) => { } }; + const handleCloseClick: React.MouseEventHandler = () => { + history.push('/timeline/fediverse'); + }; + + const handleLoadMore = (maxId: string) => { + dispatch(expandRemoteTimeline(instance, { maxId, onlyMedia })); + }; + useEffect(() => { disconnect(); dispatch(expandRemoteTimeline(instance, { onlyMedia, maxId: undefined })); @@ -55,14 +63,6 @@ const RemoteTimeline: React.FC = ({ params }) => { }; }, [onlyMedia]); - const handleCloseClick: React.MouseEventHandler = () => { - history.push('/timeline/fediverse'); - }; - - const handleLoadMore = (maxId: string) => { - dispatch(expandRemoteTimeline(instance, { maxId, onlyMedia })); - }; - return ( {instance && }