See #244 - Added notifications column settings to filter what's displayed
in the column and what appears as desktop notifications. Settings do not persist yet
This commit is contained in:
		
							parent
							
								
									75122e162d
								
							
						
					
					
						commit
						65647a2472
					
				
					 7 changed files with 221 additions and 8 deletions
				
			
		|  | @ -14,6 +14,8 @@ export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; | |||
| export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; | ||||
| export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL'; | ||||
| 
 | ||||
| export const NOTIFICATIONS_SETTING_CHANGE = 'NOTIFICATIONS_SETTING_CHANGE'; | ||||
| 
 | ||||
| const fetchRelatedRelationships = (dispatch, notifications) => { | ||||
|   const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id); | ||||
| 
 | ||||
|  | @ -23,7 +25,7 @@ const fetchRelatedRelationships = (dispatch, notifications) => { | |||
| }; | ||||
| 
 | ||||
| export function updateNotifications(notification, intlMessages, intlLocale) { | ||||
|   return dispatch => { | ||||
|   return (dispatch, getState) => { | ||||
|     dispatch({ | ||||
|       type: NOTIFICATIONS_UPDATE, | ||||
|       notification, | ||||
|  | @ -34,7 +36,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) { | |||
|     fetchRelatedRelationships(dispatch, [notification]); | ||||
| 
 | ||||
|     // Desktop notifications | ||||
|     if (typeof window.Notification !== 'undefined') { | ||||
|     if (typeof window.Notification !== 'undefined' && getState().getIn(['notifications', 'settings', 'alerts', notification.type], false)) { | ||||
|       const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); | ||||
|       const body  = $('<p>').html(notification.status ? notification.status.content : '').text(); | ||||
| 
 | ||||
|  | @ -131,3 +133,11 @@ export function expandNotificationsFail(error) { | |||
|     error | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export function changeNotificationsSetting(key, checked) { | ||||
|   return { | ||||
|     type: NOTIFICATIONS_SETTING_CHANGE, | ||||
|     key, | ||||
|     checked | ||||
|   }; | ||||
| }; | ||||
|  |  | |||
|  | @ -0,0 +1,150 @@ | |||
| import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import Toggle from 'react-toggle'; | ||||
| import { Motion, spring } from 'react-motion'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| const outerStyle = { | ||||
|   background: '#373b4a', | ||||
|   padding: '15px' | ||||
| }; | ||||
| 
 | ||||
| const iconStyle = { | ||||
|   fontSize: '16px', | ||||
|   padding: '15px', | ||||
|   position: 'absolute', | ||||
|   right: '0', | ||||
|   top: '-48px', | ||||
|   cursor: 'pointer' | ||||
| }; | ||||
| 
 | ||||
| const labelStyle = { | ||||
|   display: 'block', | ||||
|   lineHeight: '24px', | ||||
|   verticalAlign: 'middle' | ||||
| }; | ||||
| 
 | ||||
| const labelSpanStyle = { | ||||
|   display: 'inline-block', | ||||
|   verticalAlign: 'middle', | ||||
|   marginBottom: '14px', | ||||
|   marginLeft: '8px', | ||||
|   color: '#9baec8' | ||||
| }; | ||||
| 
 | ||||
| const sectionStyle = { | ||||
|   cursor: 'default', | ||||
|   display: 'block', | ||||
|   fontWeight: '500', | ||||
|   color: '#9baec8', | ||||
|   marginBottom: '10px' | ||||
| }; | ||||
| 
 | ||||
| const rowStyle = { | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| const ColumnSettings = React.createClass({ | ||||
| 
 | ||||
|   propTypes: { | ||||
|     settings: ImmutablePropTypes.map.isRequired, | ||||
|     onChange: React.PropTypes.func.isRequired | ||||
|   }, | ||||
| 
 | ||||
|   getInitialState () { | ||||
|     return { | ||||
|       collapsed: true | ||||
|     }; | ||||
|   }, | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   handleToggleCollapsed () { | ||||
|     this.setState({ collapsed: !this.state.collapsed }); | ||||
|   }, | ||||
| 
 | ||||
|   handleChange (key, e) { | ||||
|     this.props.onChange(key, e.target.checked); | ||||
|   }, | ||||
| 
 | ||||
|   render () { | ||||
|     const { settings }  = this.props; | ||||
|     const { collapsed } = this.state; | ||||
| 
 | ||||
|     const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />; | ||||
|     const showStr  = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />; | ||||
| 
 | ||||
|     return ( | ||||
|       <div style={{ position: 'relative' }}> | ||||
|         <div style={{...iconStyle, color: collapsed ? '#9baec8' : '#fff', background: collapsed ? '#2f3441' : '#373b4a' }} onClick={this.handleToggleCollapsed}><i className='fa fa-sliders' /></div> | ||||
| 
 | ||||
|         <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : 458) }}> | ||||
|           {({ opacity, height }) => | ||||
|             <div style={{ overflow: 'hidden', height: `${height}px`, opacity: opacity / 100 }}> | ||||
|               <div style={outerStyle}> | ||||
|                 <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span> | ||||
| 
 | ||||
|                 <div style={rowStyle}> | ||||
|                   <label style={labelStyle}> | ||||
|                     <Toggle checked={settings.getIn(['alerts', 'follow'])} onChange={this.handleChange.bind(this, ['alerts', 'follow'])} /> | ||||
|                     <span style={labelSpanStyle}>{alertStr}</span> | ||||
|                   </label> | ||||
| 
 | ||||
|                   <label style={labelStyle}> | ||||
|                     <Toggle checked={settings.getIn(['shows', 'follow'])} onChange={this.handleChange.bind(this, ['shows', 'follow'])} /> | ||||
|                     <span style={labelSpanStyle}>{showStr}</span> | ||||
|                   </label> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span> | ||||
| 
 | ||||
|                 <div style={rowStyle}> | ||||
|                   <label style={labelStyle}> | ||||
|                     <Toggle checked={settings.getIn(['alerts', 'favourite'])} onChange={this.handleChange.bind(this, ['alerts', 'favourite'])} /> | ||||
|                     <span style={labelSpanStyle}>{alertStr}</span> | ||||
|                   </label> | ||||
| 
 | ||||
|                   <label style={labelStyle}> | ||||
|                     <Toggle checked={settings.getIn(['shows', 'favourite'])} onChange={this.handleChange.bind(this, ['shows', 'favourite'])} /> | ||||
|                     <span style={labelSpanStyle}>{showStr}</span> | ||||
|                   </label> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span> | ||||
| 
 | ||||
|                 <div style={rowStyle}> | ||||
|                   <label style={labelStyle}> | ||||
|                     <Toggle checked={settings.getIn(['alerts', 'mention'])} onChange={this.handleChange.bind(this, ['alerts', 'mention'])} /> | ||||
|                     <span style={labelSpanStyle}>{alertStr}</span> | ||||
|                   </label> | ||||
| 
 | ||||
|                   <label style={labelStyle}> | ||||
|                     <Toggle checked={settings.getIn(['shows', 'mention'])} onChange={this.handleChange.bind(this, ['shows', 'mention'])} /> | ||||
|                     <span style={labelSpanStyle}>{showStr}</span> | ||||
|                   </label> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span> | ||||
| 
 | ||||
|                 <div style={rowStyle}> | ||||
|                   <label style={labelStyle}> | ||||
|                     <Toggle checked={settings.getIn(['alerts', 'reblog'])} onChange={this.handleChange.bind(this, ['alerts', 'reblog'])} /> | ||||
|                     <span style={labelSpanStyle}>{alertStr}</span> | ||||
|                   </label> | ||||
| 
 | ||||
|                   <label style={labelStyle}> | ||||
|                     <Toggle checked={settings.getIn(['shows', 'reblog'])} onChange={this.handleChange.bind(this, ['shows', 'reblog'])} /> | ||||
|                     <span style={labelSpanStyle}>{showStr}</span> | ||||
|                   </label> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           } | ||||
|         </Motion> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default ColumnSettings; | ||||
|  | @ -0,0 +1,17 @@ | |||
| import { connect } from 'react-redux'; | ||||
| import ColumnSettings from '../components/column_settings'; | ||||
| import { changeNotificationsSetting } from '../../../actions/notifications'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   settings: state.getIn(['notifications', 'settings']) | ||||
| }); | ||||
| 
 | ||||
| const mapDispatchToProps = dispatch => ({ | ||||
| 
 | ||||
|   onChange (key, checked) { | ||||
|     dispatch(changeNotificationsSetting(key, checked)); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); | ||||
|  | @ -9,13 +9,21 @@ import { | |||
| import NotificationContainer from './containers/notification_container'; | ||||
| import { ScrollContainer } from 'react-router-scroll'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ColumnSettingsContainer from './containers/column_settings_container'; | ||||
| import { createSelector } from 'reselect'; | ||||
| import Immutable from 'immutable'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   title: { id: 'column.notifications', defaultMessage: 'Notifications' } | ||||
| }); | ||||
| 
 | ||||
| const getNotifications = createSelector([ | ||||
|   state => Immutable.List(state.getIn(['notifications', 'settings', 'shows']).filter(item => !item).keys()), | ||||
|   state => state.getIn(['notifications', 'items']) | ||||
| ], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type')))); | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   notifications: state.getIn(['notifications', 'items']) | ||||
|   notifications: getNotifications(state) | ||||
| }); | ||||
| 
 | ||||
| const Notifications = React.createClass({ | ||||
|  | @ -23,7 +31,8 @@ const Notifications = React.createClass({ | |||
|   propTypes: { | ||||
|     notifications: ImmutablePropTypes.list.isRequired, | ||||
|     dispatch: React.PropTypes.func.isRequired, | ||||
|     trackScroll: React.PropTypes.bool | ||||
|     trackScroll: React.PropTypes.bool, | ||||
|     intl: React.PropTypes.object.isRequired | ||||
|   }, | ||||
| 
 | ||||
|   getDefaultProps () { | ||||
|  | @ -69,6 +78,7 @@ const Notifications = React.createClass({ | |||
|     } else { | ||||
|       return ( | ||||
|         <Column icon='bell' heading={intl.formatMessage(messages.title)}> | ||||
|           <ColumnSettingsContainer /> | ||||
|           {scrollableArea} | ||||
|         </Column> | ||||
|       ); | ||||
|  |  | |||
|  | @ -40,7 +40,8 @@ const Column = React.createClass({ | |||
| 
 | ||||
|   propTypes: { | ||||
|     heading: React.PropTypes.string, | ||||
|     icon: React.PropTypes.string | ||||
|     icon: React.PropTypes.string, | ||||
|     children: React.PropTypes.node | ||||
|   }, | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
|  |  | |||
|  | @ -52,7 +52,13 @@ const en = { | |||
|   "notification.follow": "{name} followed you", | ||||
|   "notification.favourite": "{name} favourited your status", | ||||
|   "notification.reblog": "{name} boosted your status", | ||||
|   "notification.mention": "{name} mentioned you" | ||||
|   "notification.mention": "{name} mentioned you", | ||||
|   "notifications.column_settings.alert": "Desktop notifications", | ||||
|   "notifications.column_settings.show": "Show in column", | ||||
|   "notifications.column_settings.follow": "New followers:", | ||||
|   "notifications.column_settings.favourite": "Favourites:", | ||||
|   "notifications.column_settings.mention": "Mentions:", | ||||
|   "notifications.column_settings.reblog": "Boosts:", | ||||
| }; | ||||
| 
 | ||||
| export default en; | ||||
|  |  | |||
|  | @ -1,7 +1,8 @@ | |||
| import { | ||||
|   NOTIFICATIONS_UPDATE, | ||||
|   NOTIFICATIONS_REFRESH_SUCCESS, | ||||
|   NOTIFICATIONS_EXPAND_SUCCESS | ||||
|   NOTIFICATIONS_EXPAND_SUCCESS, | ||||
|   NOTIFICATIONS_SETTING_CHANGE | ||||
| } from '../actions/notifications'; | ||||
| import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts'; | ||||
| import Immutable from 'immutable'; | ||||
|  | @ -9,7 +10,23 @@ import Immutable from 'immutable'; | |||
| const initialState = Immutable.Map({ | ||||
|   items: Immutable.List(), | ||||
|   next: null, | ||||
|   loaded: false | ||||
|   loaded: false, | ||||
| 
 | ||||
|   settings: Immutable.Map({ | ||||
|     alerts: Immutable.Map({ | ||||
|       follow: true, | ||||
|       favourite: true, | ||||
|       reblog: true, | ||||
|       mention: true | ||||
|     }), | ||||
| 
 | ||||
|     shows: Immutable.Map({ | ||||
|       follow: true, | ||||
|       favourite: true, | ||||
|       reblog: true, | ||||
|       mention: true | ||||
|     }) | ||||
|   }) | ||||
| }); | ||||
| 
 | ||||
| const notificationToMap = notification => Immutable.Map({ | ||||
|  | @ -58,6 +75,8 @@ export default function notifications(state = initialState, action) { | |||
|       return appendNormalizedNotifications(state, action.notifications, action.next); | ||||
|     case ACCOUNT_BLOCK_SUCCESS: | ||||
|       return filterNotifications(state, action.relationship); | ||||
|     case NOTIFICATIONS_SETTING_CHANGE: | ||||
|       return state.setIn(['settings', ...action.key], action.checked); | ||||
|     default: | ||||
|       return state; | ||||
|   } | ||||
|  |  | |||
		Reference in a new issue