Add sounds for notifications. Boop by @jk@mastodon.social

master
Eugen Rochko 8 years ago
parent d7a7baa9a7
commit fcb5a85cdd
  1. 1
      Procfile
  2. 8
      app/assets/javascripts/components/actions/notifications.jsx
  3. 2
      app/assets/javascripts/components/components/column_collapsable.jsx
  4. 7
      app/assets/javascripts/components/features/notifications/components/column_settings.jsx
  5. 9
      app/assets/javascripts/components/reducers/settings.jsx
  6. 14
      app/assets/javascripts/components/store/configureStore.jsx
  7. 44
      config/puma.rb
  8. 1
      package.json
  9. BIN
      public/sounds/boop.mp3
  10. 10
      yarn.lock

@ -1 +1,2 @@
web: bundle exec puma -C config/puma.rb web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -q default -q mailers -q push

@ -24,17 +24,21 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
export function updateNotifications(notification, intlMessages, intlLocale) { export function updateNotifications(notification, intlMessages, intlLocale) {
return (dispatch, getState) => { return (dispatch, getState) => {
const showAlert = getState().getIn(['notifications', 'settings', 'alerts', notification.type], false);
const playSound = getState().getIn(['notifications', 'settings', 'sounds', notification.type], false);
dispatch({ dispatch({
type: NOTIFICATIONS_UPDATE, type: NOTIFICATIONS_UPDATE,
notification, notification,
account: notification.account, account: notification.account,
status: notification.status status: notification.status,
meta: playSound ? { sound: 'boop' } : null
}); });
fetchRelatedRelationships(dispatch, [notification]); fetchRelatedRelationships(dispatch, [notification]);
// Desktop notifications // Desktop notifications
if (typeof window.Notification !== 'undefined' && getState().getIn(['notifications', 'settings', 'alerts', notification.type], false)) { if (typeof window.Notification !== 'undefined' && showAlert) {
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 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(); const body = $('<p>').html(notification.status ? notification.status.content : '').text();

@ -45,7 +45,7 @@ const ColumnCollapsable = React.createClass({
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<div style={{...iconStyle, color: collapsed ? '#9baec8' : '#fff', background: collapsed ? '#2f3441' : '#373b4a' }} onClick={this.handleToggleCollapsed}><i className={`fa fa-${icon}`} /></div> <div style={{...iconStyle, color: collapsed ? '#9baec8' : '#fff', background: collapsed ? '#2f3441' : '#373b4a' }} onClick={this.handleToggleCollapsed}><i className={`fa fa-${icon}`} /></div>
<Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : fullHeight, { stiffness: 150, damping: 9 }) }}> <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : fullHeight, collapsed ? undefined : { stiffness: 150, damping: 9 }) }}>
{({ opacity, height }) => {({ opacity, height }) =>
<div style={{ overflow: 'hidden', height: `${height}px`, opacity: opacity / 100 }}> <div style={{ overflow: 'hidden', height: `${height}px`, opacity: opacity / 100 }}>
{children} {children}

@ -36,15 +36,17 @@ const ColumnSettings = React.createClass({
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />; const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />; const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
return ( return (
<ColumnCollapsable icon='sliders' fullHeight={458} onCollapse={onSave}> <ColumnCollapsable icon='sliders' fullHeight={616} onCollapse={onSave}>
<div style={outerStyle}> <div style={outerStyle}>
<span style={sectionStyle}><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span> <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
<div style={rowStyle}> <div style={rowStyle}>
<SettingToggle settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} /> <SettingToggle settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} /> <SettingToggle settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div> </div>
<span style={sectionStyle}><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span> <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
@ -52,6 +54,7 @@ const ColumnSettings = React.createClass({
<div style={rowStyle}> <div style={rowStyle}>
<SettingToggle settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} /> <SettingToggle settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} /> <SettingToggle settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
<SettingToggle settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
</div> </div>
<span style={sectionStyle}><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span> <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
@ -59,6 +62,7 @@ const ColumnSettings = React.createClass({
<div style={rowStyle}> <div style={rowStyle}>
<SettingToggle settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} /> <SettingToggle settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} /> <SettingToggle settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingToggle settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
</div> </div>
<span style={sectionStyle}><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span> <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
@ -66,6 +70,7 @@ const ColumnSettings = React.createClass({
<div style={rowStyle}> <div style={rowStyle}>
<SettingToggle settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} /> <SettingToggle settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} /> <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
<SettingToggle settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
</div> </div>
</div> </div>
</ColumnCollapsable> </ColumnCollapsable>

@ -23,6 +23,13 @@ const initialState = Immutable.Map({
favourite: true, favourite: true,
reblog: true, reblog: true,
mention: true mention: true
}),
sounds: Immutable.Map({
follow: true,
favourite: true,
reblog: true,
mention: true
}) })
}) })
}); });
@ -30,7 +37,7 @@ const initialState = Immutable.Map({
export default function settings(state = initialState, action) { export default function settings(state = initialState, action) {
switch(action.type) { switch(action.type) {
case STORE_HYDRATE: case STORE_HYDRATE:
return state.merge(action.state.get('settings')); return state.mergeDeep(action.state.get('settings'));
case SETTING_CHANGE: case SETTING_CHANGE:
return state.setIn(action.key, action.value); return state.setIn(action.key, action.value);
default: default:

@ -3,10 +3,18 @@ import thunk from 'redux-thunk';
import appReducer from '../reducers'; import appReducer from '../reducers';
import loadingBarMiddleware from '../middleware/loading_bar'; import loadingBarMiddleware from '../middleware/loading_bar';
import errorsMiddleware from '../middleware/errors'; import errorsMiddleware from '../middleware/errors';
import soundsMiddleware from 'redux-sounds';
import Immutable from 'immutable'; import Immutable from 'immutable';
const soundsData = {
boop: '/sounds/boop.mp3'
};
export default function configureStore() { export default function configureStore() {
return createStore(appReducer, compose(applyMiddleware(thunk, loadingBarMiddleware({ return createStore(appReducer, compose(applyMiddleware(
promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'], thunk,
}), errorsMiddleware()), window.devToolsExtension ? window.devToolsExtension() : f => f)); loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
errorsMiddleware(),
soundsMiddleware(soundsData)
), window.devToolsExtension ? window.devToolsExtension() : f => f));
}; };

@ -1,52 +1,18 @@
# Puma can serve each request in a thread from an internal thread pool. threads_count = ENV.fetch('MAX_THREADS') { 5 }.to_i
# The `threads` method setting takes two numbers a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum, this matches the default thread size of Active Record.
#
threads_count = ENV.fetch("MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count threads threads_count, threads_count
# Specifies the `port` that Puma will listen on to receive requests, default is 3000. port ENV.fetch('PORT') { 3000 }
# environment ENV.fetch('RAILS_ENV') { 'development' }
port ENV.fetch("PORT") { 3000 } workers ENV.fetch('WEB_CONCURRENCY') { 2 }
# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }
# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked webserver processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory. If you use this option
# you need to make sure to reconnect any threads in the `on_worker_boot`
# block.
#
preload_app! preload_app!
# The code in the `on_worker_boot` will be called if you are using
# clustered mode by specifying a number of `workers`. After each worker
# process is booted this block will be run, if you are using `preload_app!`
# option you will want to use this block to reconnect to any threads
# or connections that may have been created at application boot, Ruby
# cannot share connections between processes.
#
on_worker_boot do on_worker_boot do
if ENV['HEROKU'] # Spawn the workers from Puma, to only use one dyno
if ENV["HEROKU"] #Spwan the workers from Puma, to only use one dyno
@sidekiq_pid ||= spawn('bundle exec sidekiq -q default -q mailers -q push') @sidekiq_pid ||= spawn('bundle exec sidekiq -q default -q mailers -q push')
end end
ActiveRecord::Base.establish_connection if defined?(ActiveRecord) ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end end
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart plugin :tmp_restart

@ -49,6 +49,7 @@
"react-toggle": "^2.1.1", "react-toggle": "^2.1.1",
"redux": "^3.6.0", "redux": "^3.6.0",
"redux-immutable": "^3.0.8", "redux-immutable": "^3.0.8",
"redux-sounds": "^1.1.1",
"redux-thunk": "^2.1.0", "redux-thunk": "^2.1.0",
"reselect": "^2.5.4", "reselect": "^2.5.4",
"sass-loader": "^4.0.2", "sass-loader": "^4.0.2",

Binary file not shown.

@ -2499,6 +2499,10 @@ hosted-git-info@^2.1.4:
version "2.1.5" version "2.1.5"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b"
howler@^1.1.28:
version "1.1.29"
resolved "https://registry.yarnpkg.com/howler/-/howler-1.1.29.tgz#9a3a7fa69e9b9d805c65ad98f66e35893a597b63"
html-comment-regex@^1.1.0: html-comment-regex@^1.1.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
@ -4466,6 +4470,12 @@ redux-immutable@^3.0.8:
dependencies: dependencies:
immutable "^3.7.6" immutable "^3.7.6"
redux-sounds@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/redux-sounds/-/redux-sounds-1.1.1.tgz#7a31052dbc617d419c53056215865762f44adb7e"
dependencies:
howler "^1.1.28"
redux-thunk@^2.1.0: redux-thunk@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.1.0.tgz#c724bfee75dbe352da2e3ba9bc14302badd89a98" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.1.0.tgz#c724bfee75dbe352da2e3ba9bc14302badd89a98"

Loading…
Cancel
Save