diff --git a/app/javascript/mastodon/features/notifications/components/checkbox_with_label.jsx b/app/javascript/mastodon/features/notifications/components/checkbox_with_label.jsx deleted file mode 100644 index 5a7bb2307..000000000 --- a/app/javascript/mastodon/features/notifications/components/checkbox_with_label.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types'; -import { useCallback } from 'react'; - -import Toggle from 'react-toggle'; - -export const CheckboxWithLabel = ({ checked, disabled, children, onChange }) => { - const handleChange = useCallback(({ target }) => { - onChange(target.checked); - }, [onChange]); - - return ( - - ); -}; - -CheckboxWithLabel.propTypes = { - checked: PropTypes.bool, - disabled: PropTypes.bool, - children: PropTypes.children, - onChange: PropTypes.func, -}; diff --git a/app/javascript/mastodon/features/notifications/components/checkbox_with_label.tsx b/app/javascript/mastodon/features/notifications/components/checkbox_with_label.tsx new file mode 100644 index 000000000..9922bc6c7 --- /dev/null +++ b/app/javascript/mastodon/features/notifications/components/checkbox_with_label.tsx @@ -0,0 +1,40 @@ +import type { PropsWithChildren } from 'react'; +import { useCallback } from 'react'; + +import Toggle from 'react-toggle'; + +interface Props { + checked: boolean; + disabled?: boolean; + onChange: (checked: boolean) => void; +} + +export const CheckboxWithLabel: React.FC> = ({ + checked, + disabled, + children, + onChange, +}) => { + const handleChange = useCallback( + ({ target }: React.ChangeEvent) => { + onChange(target.checked); + }, + [onChange], + ); + + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.jsx b/app/javascript/mastodon/features/notifications/components/column_settings.jsx index d35e62f8f..d511c5365 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.jsx +++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx @@ -8,9 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions'; -import { CheckboxWithLabel } from './checkbox_with_label'; import ClearColumnButton from './clear_column_button'; import GrantPermissionButton from './grant_permission_button'; +import { PolicyControls } from './policy_controls'; import SettingToggle from './setting_toggle'; class ColumnSettings extends PureComponent { @@ -24,32 +24,14 @@ class ColumnSettings extends PureComponent { alertsEnabled: PropTypes.bool, browserSupport: PropTypes.bool, browserPermission: PropTypes.string, - notificationPolicy: PropTypes.object.isRequired, - onChangePolicy: PropTypes.func.isRequired, }; onPushChange = (path, checked) => { this.props.onChange(['push', ...path], checked); }; - handleFilterNotFollowing = checked => { - this.props.onChangePolicy('filter_not_following', checked); - }; - - handleFilterNotFollowers = checked => { - this.props.onChangePolicy('filter_not_followers', checked); - }; - - handleFilterNewAccounts = checked => { - this.props.onChangePolicy('filter_new_accounts', checked); - }; - - handleFilterPrivateMentions = checked => { - this.props.onChangePolicy('filter_private_mentions', checked); - }; - render () { - const { settings, pushSettings, onChange, onClear, alertsEnabled, browserSupport, browserPermission, onRequestNotificationPermission, notificationPolicy } = this.props; + const { settings, pushSettings, onChange, onClear, alertsEnabled, browserSupport, browserPermission, onRequestNotificationPermission } = this.props; const filterAdvancedStr = ; const unreadMarkersShowStr = ; @@ -79,31 +61,7 @@ class ColumnSettings extends PureComponent { )} -
-

- -
- - - - - - - - - - - - - - - - - - - -
-
+

diff --git a/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx b/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx index 9f93d25c5..d9e60d48c 100644 --- a/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx +++ b/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx @@ -1,18 +1,62 @@ -import { useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl, defineMessages } from 'react-intl'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react'; import { fetchNotificationPolicy } from 'mastodon/actions/notification_policies'; import { Icon } from 'mastodon/components/icon'; +import { selectSettingsNotificationsMinimizeFilteredBanner } from 'mastodon/selectors/settings'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; -import { toCappedNumber } from 'mastodon/utils/numbers'; + +const messages = defineMessages({ + filteredNotifications: { + id: 'notification_requests.title', + defaultMessage: 'Filtered notifications', + }, +}); + +export const FilteredNotificationsIconButton: React.FC<{ + className?: string; +}> = ({ className }) => { + const intl = useIntl(); + const history = useHistory(); + const policy = useAppSelector((state) => state.notificationPolicy); + const minimizeSetting = useAppSelector( + selectSettingsNotificationsMinimizeFilteredBanner, + ); + + const handleClick = useCallback(() => { + history.push('/notifications/requests'); + }, [history]); + + if (policy === null || policy.summary.pending_notifications_count === 0) { + return null; + } + + if (!minimizeSetting) { + return null; + } + + return ( + + ); +}; export const FilteredNotificationsBanner: React.FC = () => { const dispatch = useAppDispatch(); const policy = useAppSelector((state) => state.notificationPolicy); + const minimizeSetting = useAppSelector( + selectSettingsNotificationsMinimizeFilteredBanner, + ); useEffect(() => { void dispatch(fetchNotificationPolicy()); @@ -30,6 +74,10 @@ export const FilteredNotificationsBanner: React.FC = () => { return null; } + if (minimizeSetting) { + return null; + } + return ( { /> - -
- {toCappedNumber(policy.summary.pending_notifications_count)} -
); }; diff --git a/app/javascript/mastodon/features/notifications/components/policy_controls.tsx b/app/javascript/mastodon/features/notifications/components/policy_controls.tsx new file mode 100644 index 000000000..1647bf6b4 --- /dev/null +++ b/app/javascript/mastodon/features/notifications/components/policy_controls.tsx @@ -0,0 +1,141 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { updateNotificationsPolicy } from 'mastodon/actions/notification_policies'; +import { useAppSelector, useAppDispatch } from 'mastodon/store'; + +import { CheckboxWithLabel } from './checkbox_with_label'; + +export const PolicyControls: React.FC = () => { + const dispatch = useAppDispatch(); + + const notificationPolicy = useAppSelector( + (state) => state.notificationPolicy, + ); + + const handleFilterNotFollowing = useCallback( + (checked: boolean) => { + void dispatch( + updateNotificationsPolicy({ filter_not_following: checked }), + ); + }, + [dispatch], + ); + + const handleFilterNotFollowers = useCallback( + (checked: boolean) => { + void dispatch( + updateNotificationsPolicy({ filter_not_followers: checked }), + ); + }, + [dispatch], + ); + + const handleFilterNewAccounts = useCallback( + (checked: boolean) => { + void dispatch( + updateNotificationsPolicy({ filter_new_accounts: checked }), + ); + }, + [dispatch], + ); + + const handleFilterPrivateMentions = useCallback( + (checked: boolean) => { + void dispatch( + updateNotificationsPolicy({ filter_private_mentions: checked }), + ); + }, + [dispatch], + ); + + if (!notificationPolicy) return null; + + return ( +
+

+ +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ); +}; diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js index 21dbb2aa9..8bcc7ab4e 100644 --- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js @@ -6,7 +6,6 @@ import { openModal } from 'mastodon/actions/modal'; import { initializeNotifications } from 'mastodon/actions/notifications_migration'; import { showAlert } from '../../../actions/alerts'; -import { updateNotificationsPolicy } from '../../../actions/notification_policies'; import { setFilter, requestBrowserPermission } from '../../../actions/notifications'; import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications'; import { changeSetting } from '../../../actions/settings'; @@ -25,7 +24,6 @@ const mapStateToProps = state => ({ alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true), browserSupport: state.getIn(['notifications', 'browserSupport']), browserPermission: state.getIn(['notifications', 'browserPermission']), - notificationPolicy: state.notificationPolicy, }); const mapDispatchToProps = (dispatch) => ({ @@ -74,12 +72,6 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(requestBrowserPermission()); }, - onChangePolicy (param, checked) { - dispatch(updateNotificationsPolicy({ - [param]: checked, - })); - }, - }); export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ColumnSettings)); diff --git a/app/javascript/mastodon/features/notifications/index.jsx b/app/javascript/mastodon/features/notifications/index.jsx index f5ebe6fe9..cefbd544b 100644 --- a/app/javascript/mastodon/features/notifications/index.jsx +++ b/app/javascript/mastodon/features/notifications/index.jsx @@ -34,7 +34,10 @@ import ColumnHeader from '../../components/column_header'; import { LoadGap } from '../../components/load_gap'; import ScrollableList from '../../components/scrollable_list'; -import { FilteredNotificationsBanner } from './components/filtered_notifications_banner'; +import { + FilteredNotificationsBanner, + FilteredNotificationsIconButton, +} from './components/filtered_notifications_banner'; import NotificationsPermissionBanner from './components/notifications_permission_banner'; import ColumnSettingsContainer from './containers/column_settings_container'; import FilterBarContainer from './containers/filter_bar_container'; @@ -255,20 +258,21 @@ class Notifications extends PureComponent { scrollContainer = ; } - let extraButton = null; - - if (canMarkAsRead) { - extraButton = ( - - ); - } + const extraButton = ( + <> + + {canMarkAsRead && ( + + )} + + ); return ( diff --git a/app/javascript/mastodon/features/notifications/requests.jsx b/app/javascript/mastodon/features/notifications/requests.jsx index 2d10c6a06..66db35b58 100644 --- a/app/javascript/mastodon/features/notifications/requests.jsx +++ b/app/javascript/mastodon/features/notifications/requests.jsx @@ -9,16 +9,52 @@ import { useSelector, useDispatch } from 'react-redux'; import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react'; import { fetchNotificationRequests, expandNotificationRequests } from 'mastodon/actions/notifications'; +import { changeSetting } from 'mastodon/actions/settings'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; import ScrollableList from 'mastodon/components/scrollable_list'; import { NotificationRequest } from './components/notification_request'; +import { PolicyControls } from './components/policy_controls'; +import SettingToggle from './components/setting_toggle'; const messages = defineMessages({ title: { id: 'notification_requests.title', defaultMessage: 'Filtered notifications' }, + maximize: { id: 'notification_requests.maximize', defaultMessage: 'Maximize' } }); +const ColumnSettings = () => { + const dispatch = useDispatch(); + const settings = useSelector((state) => state.settings.get('notifications')); + + const onChange = useCallback( + (key, checked) => { + dispatch(changeSetting(['notifications', ...key], checked)); + }, + [dispatch], + ); + + return ( +
+
+
+ + } + /> +
+
+ + +
+ ); +}; + export const NotificationRequests = ({ multiColumn }) => { const columnRef = useRef(); const intl = useIntl(); @@ -48,7 +84,9 @@ export const NotificationRequests = ({ multiColumn }) => { onClick={handleHeaderClick} multiColumn={multiColumn} showBackButton - /> + > + + ); - const extraButton = canMarkAsRead ? ( - - ) : null; + const extraButton = ( + <> + + {canMarkAsRead && ( + + )} + + ); return ( 'dismissPermissionBanner', ])) as boolean; +export const selectSettingsNotificationsMinimizeFilteredBanner = ( + state: RootState, +) => + state.settings.getIn(['notifications', 'minimizeFilteredBanner']) as boolean; + /* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */