aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2017-11-10 12:08:35 +0100
committerLibravatar Stefan Malzner <stefan@adlk.io>2017-11-10 12:08:35 +0100
commitf5a9aa21e2ab958f60c143668f4836bc47e2b539 (patch)
tree7d7cb70dee56e6ca4a927f7789601cc428381659
parentfeat(Service): Add option to mute service (diff)
downloadferdium-app-f5a9aa21e2ab958f60c143668f4836bc47e2b539.tar.gz
ferdium-app-f5a9aa21e2ab958f60c143668f4836bc47e2b539.tar.zst
ferdium-app-f5a9aa21e2ab958f60c143668f4836bc47e2b539.zip
feat(App): Add option to mute all services in sidebar
Closes #8 #162
-rw-r--r--src/actions/app.js4
-rw-r--r--src/actions/service.js3
-rw-r--r--src/components/layout/Sidebar.js52
-rw-r--r--src/components/services/content/ServiceWebview.js4
-rw-r--r--src/components/services/content/Services.js3
-rw-r--r--src/components/services/tabs/TabBarSortableList.js22
-rw-r--r--src/components/services/tabs/TabItem.js15
-rw-r--r--src/components/services/tabs/Tabbar.js3
-rw-r--r--src/config.js1
-rw-r--r--src/containers/layout/AppLayoutContainer.js16
-rw-r--r--src/i18n/locales/en-US.json4
-rw-r--r--src/stores/AppStore.js14
-rw-r--r--src/stores/ServicesStore.js17
-rw-r--r--src/styles/layout.scss32
-rw-r--r--src/styles/tabs.scss7
15 files changed, 139 insertions, 58 deletions
diff --git a/src/actions/app.js b/src/actions/app.js
index 5db4b739e..25ff9344d 100644
--- a/src/actions/app.js
+++ b/src/actions/app.js
@@ -20,4 +20,8 @@ export default {
20 resetUpdateStatus: {}, 20 resetUpdateStatus: {},
21 installUpdate: {}, 21 installUpdate: {},
22 healthCheck: {}, 22 healthCheck: {},
23 muteApp: {
24 isMuted: PropTypes.bool.isRequired,
25 },
26 toggleMuteApp: {},
23}; 27};
diff --git a/src/actions/service.js b/src/actions/service.js
index ea6ea5acc..1b918251b 100644
--- a/src/actions/service.js
+++ b/src/actions/service.js
@@ -71,6 +71,9 @@ export default {
71 toggleNotifications: { 71 toggleNotifications: {
72 serviceId: PropTypes.string.isRequired, 72 serviceId: PropTypes.string.isRequired,
73 }, 73 },
74 toggleAudio: {
75 serviceId: PropTypes.string.isRequired,
76 },
74 openDevTools: { 77 openDevTools: {
75 serviceId: PropTypes.string.isRequired, 78 serviceId: PropTypes.string.isRequired,
76 }, 79 },
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js
index 6a5c0f365..72ee2b3b7 100644
--- a/src/components/layout/Sidebar.js
+++ b/src/components/layout/Sidebar.js
@@ -11,16 +11,25 @@ const messages = defineMessages({
11 id: 'sidebar.settings', 11 id: 'sidebar.settings',
12 defaultMessage: '!!!Settings', 12 defaultMessage: '!!!Settings',
13 }, 13 },
14 addNewService: {
15 id: 'sidebar.addNewService',
16 defaultMessage: '!!!Add new service',
17 },
18 mute: {
19 id: 'sidebar.mute',
20 defaultMessage: '!!!Disable audio',
21 },
22 unmute: {
23 id: 'sidebar.unmute',
24 defaultMessage: '!!!Enable audio',
25 },
14}); 26});
15 27
16export default class Sidebar extends Component { 28export default class Sidebar extends Component {
17 static propTypes = { 29 static propTypes = {
18 openSettings: PropTypes.func.isRequired, 30 openSettings: PropTypes.func.isRequired,
19 isPremiumUser: PropTypes.bool, 31 toggleMuteApp: PropTypes.func.isRequired,
20 } 32 isAppMuted: PropTypes.bool.isRequired,
21
22 static defaultProps = {
23 isPremiumUser: false,
24 } 33 }
25 34
26 static contextTypes = { 35 static contextTypes = {
@@ -40,8 +49,9 @@ export default class Sidebar extends Component {
40 } 49 }
41 50
42 render() { 51 render() {
43 const { openSettings, isPremiumUser } = this.props; 52 const { openSettings, toggleMuteApp, isAppMuted } = this.props;
44 const { intl } = this.context; 53 const { intl } = this.context;
54
45 return ( 55 return (
46 <div className="sidebar"> 56 <div className="sidebar">
47 <Tabbar 57 <Tabbar
@@ -50,21 +60,25 @@ export default class Sidebar extends Component {
50 disableToolTip={() => this.disableToolTip()} 60 disableToolTip={() => this.disableToolTip()}
51 /> 61 />
52 <button 62 <button
53 onClick={openSettings} 63 onClick={toggleMuteApp}
54 className="sidebar__settings-button" 64 className={`sidebar__button ${isAppMuted ? 'is-muted' : ''}`}
65 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`}
66 >
67 <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} />
68 </button>
69 <button
70 onClick={() => openSettings({ path: 'recipes' })}
71 className="sidebar__button"
72 data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`}
73 >
74 <i className="mdi mdi-plus-box" />
75 </button>
76 <button
77 onClick={() => openSettings({ path: 'app' })}
78 className="sidebar__button"
55 data-tip={`${intl.formatMessage(messages.settings)} (${ctrlKey}+,)`} 79 data-tip={`${intl.formatMessage(messages.settings)} (${ctrlKey}+,)`}
56 > 80 >
57 {isPremiumUser && ( 81 <i className="mdi mdi-settings" />
58 <span className="emoji">
59 <img src="./assets/images/emoji/star.png" alt="" />
60 </span>
61 )}
62 <img
63 src="./assets/images/logo.svg"
64 className="sidebar__logo"
65 alt=""
66 />
67 {intl.formatMessage(messages.settings)}
68 </button> 82 </button>
69 {this.state.tooltipEnabled && ( 83 {this.state.tooltipEnabled && (
70 <ReactTooltip place="right" type="dark" effect="solid" /> 84 <ReactTooltip place="right" type="dark" effect="solid" />
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index d7e0a4f38..60bdf7e47 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -15,6 +15,7 @@ export default class ServiceWebview extends Component {
15 service: PropTypes.instanceOf(ServiceModel).isRequired, 15 service: PropTypes.instanceOf(ServiceModel).isRequired,
16 setWebviewReference: PropTypes.func.isRequired, 16 setWebviewReference: PropTypes.func.isRequired,
17 reload: PropTypes.func.isRequired, 17 reload: PropTypes.func.isRequired,
18 isAppMuted: PropTypes.bool.isRequired,
18 }; 19 };
19 20
20 static defaultProps = { 21 static defaultProps = {
@@ -56,6 +57,7 @@ export default class ServiceWebview extends Component {
56 service, 57 service,
57 setWebviewReference, 58 setWebviewReference,
58 reload, 59 reload,
60 isAppMuted,
59 } = this.props; 61 } = this.props;
60 62
61 const webviewClasses = classnames({ 63 const webviewClasses = classnames({
@@ -92,7 +94,7 @@ export default class ServiceWebview extends Component {
92 })} 94 })}
93 onUpdateTargetUrl={this.updateTargetUrl} 95 onUpdateTargetUrl={this.updateTargetUrl}
94 useragent={service.userAgent} 96 useragent={service.userAgent}
95 muted={service.isMuted} 97 muted={isAppMuted || service.isMuted}
96 disablewebsecurity 98 disablewebsecurity
97 allowpopups 99 allowpopups
98 /> 100 />
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index bad525d22..55a47cdd3 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -26,6 +26,7 @@ export default class Services extends Component {
26 handleIPCMessage: PropTypes.func.isRequired, 26 handleIPCMessage: PropTypes.func.isRequired,
27 openWindow: PropTypes.func.isRequired, 27 openWindow: PropTypes.func.isRequired,
28 reload: PropTypes.func.isRequired, 28 reload: PropTypes.func.isRequired,
29 isAppMuted: PropTypes.bool.isRequired,
29 }; 30 };
30 31
31 static defaultProps = { 32 static defaultProps = {
@@ -44,6 +45,7 @@ export default class Services extends Component {
44 setWebviewReference, 45 setWebviewReference,
45 openWindow, 46 openWindow,
46 reload, 47 reload,
48 isAppMuted,
47 } = this.props; 49 } = this.props;
48 const { intl } = this.context; 50 const { intl } = this.context;
49 51
@@ -76,6 +78,7 @@ export default class Services extends Component {
76 setWebviewReference={setWebviewReference} 78 setWebviewReference={setWebviewReference}
77 openWindow={openWindow} 79 openWindow={openWindow}
78 reload={() => reload({ serviceId: service.id })} 80 reload={() => reload({ serviceId: service.id })}
81 isAppMuted={isAppMuted}
79 /> 82 />
80 ))} 83 ))}
81 </div> 84 </div>
diff --git a/src/components/services/tabs/TabBarSortableList.js b/src/components/services/tabs/TabBarSortableList.js
index e5ae36419..0146f5b35 100644
--- a/src/components/services/tabs/TabBarSortableList.js
+++ b/src/components/services/tabs/TabBarSortableList.js
@@ -2,17 +2,8 @@ import React, { Component } from 'react';
2import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 2import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import { SortableContainer } from 'react-sortable-hoc'; 4import { SortableContainer } from 'react-sortable-hoc';
5import { defineMessages, intlShape } from 'react-intl';
6 5
7import TabItem from './TabItem'; 6import TabItem from './TabItem';
8import { ctrlKey } from '../../../environment';
9
10const messages = defineMessages({
11 addNewService: {
12 id: 'sidebar.addNewService',
13 defaultMessage: '!!!Add new service',
14 },
15});
16 7
17@observer 8@observer
18class TabBarSortableList extends Component { 9class TabBarSortableList extends Component {
@@ -22,27 +13,23 @@ class TabBarSortableList extends Component {
22 openSettings: PropTypes.func.isRequired, 13 openSettings: PropTypes.func.isRequired,
23 reload: PropTypes.func.isRequired, 14 reload: PropTypes.func.isRequired,
24 toggleNotifications: PropTypes.func.isRequired, 15 toggleNotifications: PropTypes.func.isRequired,
16 toggleAudio: PropTypes.func.isRequired,
25 deleteService: PropTypes.func.isRequired, 17 deleteService: PropTypes.func.isRequired,
26 disableService: PropTypes.func.isRequired, 18 disableService: PropTypes.func.isRequired,
27 } 19 }
28 20
29 static contextTypes = {
30 intl: intlShape,
31 };
32
33 render() { 21 render() {
34 const { 22 const {
35 services, 23 services,
36 setActive, 24 setActive,
37 reload, 25 reload,
38 toggleNotifications, 26 toggleNotifications,
27 toggleAudio,
39 deleteService, 28 deleteService,
40 disableService, 29 disableService,
41 openSettings, 30 openSettings,
42 } = this.props; 31 } = this.props;
43 32
44 const { intl } = this.context;
45
46 return ( 33 return (
47 <ul 34 <ul
48 className="tabs" 35 className="tabs"
@@ -56,12 +43,13 @@ class TabBarSortableList extends Component {
56 shortcutIndex={index + 1} 43 shortcutIndex={index + 1}
57 reload={() => reload({ serviceId: service.id })} 44 reload={() => reload({ serviceId: service.id })}
58 toggleNotifications={() => toggleNotifications({ serviceId: service.id })} 45 toggleNotifications={() => toggleNotifications({ serviceId: service.id })}
46 toggleAudio={() => toggleAudio({ serviceId: service.id })}
59 deleteService={() => deleteService({ serviceId: service.id })} 47 deleteService={() => deleteService({ serviceId: service.id })}
60 disableService={() => disableService({ serviceId: service.id })} 48 disableService={() => disableService({ serviceId: service.id })}
61 openSettings={openSettings} 49 openSettings={openSettings}
62 /> 50 />
63 ))} 51 ))}
64 <li> 52 {/* <li>
65 <button 53 <button
66 className="sidebar__add-service" 54 className="sidebar__add-service"
67 onClick={() => openSettings({ path: 'recipes' })} 55 onClick={() => openSettings({ path: 'recipes' })}
@@ -69,7 +57,7 @@ class TabBarSortableList extends Component {
69 > 57 >
70 <span className="mdi mdi-plus" /> 58 <span className="mdi mdi-plus" />
71 </button> 59 </button>
72 </li> 60 </li> */}
73 </ul> 61 </ul>
74 ); 62 );
75 } 63 }
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
index 9e03d2e21..7b001f6ee 100644
--- a/src/components/services/tabs/TabItem.js
+++ b/src/components/services/tabs/TabItem.js
@@ -28,6 +28,14 @@ const messages = defineMessages({
28 id: 'tabs.item.enableNotification', 28 id: 'tabs.item.enableNotification',
29 defaultMessage: '!!!Enable notifications', 29 defaultMessage: '!!!Enable notifications',
30 }, 30 },
31 disableAudio: {
32 id: 'tabs.item.disableAudio',
33 defaultMessage: '!!!Disable audio',
34 },
35 enableAudio: {
36 id: 'tabs.item.enableAudio',
37 defaultMessage: '!!!Enable audio',
38 },
31 disableService: { 39 disableService: {
32 id: 'tabs.item.disableService', 40 id: 'tabs.item.disableService',
33 defaultMessage: '!!!Disable Service', 41 defaultMessage: '!!!Disable Service',
@@ -46,6 +54,7 @@ class TabItem extends Component {
46 shortcutIndex: PropTypes.number.isRequired, 54 shortcutIndex: PropTypes.number.isRequired,
47 reload: PropTypes.func.isRequired, 55 reload: PropTypes.func.isRequired,
48 toggleNotifications: PropTypes.func.isRequired, 56 toggleNotifications: PropTypes.func.isRequired,
57 toggleAudio: PropTypes.func.isRequired,
49 openSettings: PropTypes.func.isRequired, 58 openSettings: PropTypes.func.isRequired,
50 deleteService: PropTypes.func.isRequired, 59 deleteService: PropTypes.func.isRequired,
51 disableService: PropTypes.func.isRequired, 60 disableService: PropTypes.func.isRequired,
@@ -62,6 +71,7 @@ class TabItem extends Component {
62 shortcutIndex, 71 shortcutIndex,
63 reload, 72 reload,
64 toggleNotifications, 73 toggleNotifications,
74 toggleAudio,
65 deleteService, 75 deleteService,
66 disableService, 76 disableService,
67 openSettings, 77 openSettings,
@@ -90,6 +100,11 @@ class TabItem extends Component {
90 : intl.formatMessage(messages.enableNotifications), 100 : intl.formatMessage(messages.enableNotifications),
91 click: () => toggleNotifications(), 101 click: () => toggleNotifications(),
92 }, { 102 }, {
103 label: service.isMuted
104 ? intl.formatMessage(messages.enableAudio)
105 : intl.formatMessage(messages.disableAudio),
106 click: () => toggleAudio(),
107 }, {
93 label: intl.formatMessage(messages.disableService), 108 label: intl.formatMessage(messages.disableService),
94 click: () => disableService(), 109 click: () => disableService(),
95 }, { 110 }, {
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js
index fdb2c0a59..e8cd80e33 100644
--- a/src/components/services/tabs/Tabbar.js
+++ b/src/components/services/tabs/Tabbar.js
@@ -15,6 +15,7 @@ export default class TabBar extends Component {
15 reorder: PropTypes.func.isRequired, 15 reorder: PropTypes.func.isRequired,
16 reload: PropTypes.func.isRequired, 16 reload: PropTypes.func.isRequired,
17 toggleNotifications: PropTypes.func.isRequired, 17 toggleNotifications: PropTypes.func.isRequired,
18 toggleAudio: PropTypes.func.isRequired,
18 deleteService: PropTypes.func.isRequired, 19 deleteService: PropTypes.func.isRequired,
19 updateService: PropTypes.func.isRequired, 20 updateService: PropTypes.func.isRequired,
20 } 21 }
@@ -51,6 +52,7 @@ export default class TabBar extends Component {
51 disableToolTip, 52 disableToolTip,
52 reload, 53 reload,
53 toggleNotifications, 54 toggleNotifications,
55 toggleAudio,
54 deleteService, 56 deleteService,
55 } = this.props; 57 } = this.props;
56 58
@@ -63,6 +65,7 @@ export default class TabBar extends Component {
63 onSortStart={disableToolTip} 65 onSortStart={disableToolTip}
64 reload={reload} 66 reload={reload}
65 toggleNotifications={toggleNotifications} 67 toggleNotifications={toggleNotifications}
68 toggleAudio={toggleAudio}
66 deleteService={deleteService} 69 deleteService={deleteService}
67 disableService={this.disableService} 70 disableService={this.disableService}
68 openSettings={openSettings} 71 openSettings={openSettings}
diff --git a/src/config.js b/src/config.js
index 0a4856ece..651f2e174 100644
--- a/src/config.js
+++ b/src/config.js
@@ -12,4 +12,5 @@ export const DEFAULT_APP_SETTINGS = {
12 minimizeToSystemTray: false, 12 minimizeToSystemTray: false,
13 locale: 'en-us', // TODO: Replace with proper solution once translations are in 13 locale: 'en-us', // TODO: Replace with proper solution once translations are in
14 beta: false, 14 beta: false,
15 isAppMuted: false,
15}; 16};
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index 68ad1039e..5ef4fbb35 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -7,7 +7,7 @@ import RecipesStore from '../../stores/RecipesStore';
7import ServicesStore from '../../stores/ServicesStore'; 7import ServicesStore from '../../stores/ServicesStore';
8import UIStore from '../../stores/UIStore'; 8import UIStore from '../../stores/UIStore';
9import NewsStore from '../../stores/NewsStore'; 9import NewsStore from '../../stores/NewsStore';
10import UserStore from '../../stores/UserStore'; 10import SettingsStore from '../../stores/SettingsStore';
11import RequestStore from '../../stores/RequestStore'; 11import RequestStore from '../../stores/RequestStore';
12import GlobalErrorStore from '../../stores/GlobalErrorStore'; 12import GlobalErrorStore from '../../stores/GlobalErrorStore';
13 13
@@ -29,8 +29,8 @@ export default class AppLayoutContainer extends Component {
29 services, 29 services,
30 ui, 30 ui,
31 news, 31 news,
32 settings,
32 globalError, 33 globalError,
33 user,
34 requests, 34 requests,
35 } = this.props.stores; 35 } = this.props.stores;
36 36
@@ -43,6 +43,7 @@ export default class AppLayoutContainer extends Component {
43 reorder, 43 reorder,
44 reload, 44 reload,
45 toggleNotifications, 45 toggleNotifications,
46 toggleAudio,
46 deleteService, 47 deleteService,
47 updateService, 48 updateService,
48 } = this.props.actions.service; 49 } = this.props.actions.service;
@@ -53,6 +54,7 @@ export default class AppLayoutContainer extends Component {
53 54
54 const { 55 const {
55 installUpdate, 56 installUpdate,
57 toggleMuteApp,
56 } = this.props.actions.app; 58 } = this.props.actions.app;
57 59
58 const { 60 const {
@@ -79,14 +81,16 @@ export default class AppLayoutContainer extends Component {
79 <Sidebar 81 <Sidebar
80 services={allServices} 82 services={allServices}
81 setActive={setActive} 83 setActive={setActive}
84 isAppMuted={settings.all.isMuted}
82 openSettings={openSettings} 85 openSettings={openSettings}
83 closeSettings={closeSettings} 86 closeSettings={closeSettings}
84 reorder={reorder} 87 reorder={reorder}
85 reload={reload} 88 reload={reload}
86 toggleNotifications={toggleNotifications} 89 toggleNotifications={toggleNotifications}
90 toggleAudio={toggleAudio}
87 deleteService={deleteService} 91 deleteService={deleteService}
88 updateService={updateService} 92 updateService={updateService}
89 isPremiumUser={user.data.isPremium} 93 toggleMuteApp={toggleMuteApp}
90 /> 94 />
91 ); 95 );
92 96
@@ -97,6 +101,7 @@ export default class AppLayoutContainer extends Component {
97 setWebviewReference={setWebviewReference} 101 setWebviewReference={setWebviewReference}
98 openWindow={openWindow} 102 openWindow={openWindow}
99 reload={reload} 103 reload={reload}
104 isAppMuted={settings.all.isMuted}
100 /> 105 />
101 ); 106 );
102 107
@@ -130,7 +135,7 @@ AppLayoutContainer.wrappedComponent.propTypes = {
130 app: PropTypes.instanceOf(AppStore).isRequired, 135 app: PropTypes.instanceOf(AppStore).isRequired,
131 ui: PropTypes.instanceOf(UIStore).isRequired, 136 ui: PropTypes.instanceOf(UIStore).isRequired,
132 news: PropTypes.instanceOf(NewsStore).isRequired, 137 news: PropTypes.instanceOf(NewsStore).isRequired,
133 user: PropTypes.instanceOf(UserStore).isRequired, 138 settings: PropTypes.instanceOf(SettingsStore).isRequired,
134 requests: PropTypes.instanceOf(RequestStore).isRequired, 139 requests: PropTypes.instanceOf(RequestStore).isRequired,
135 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, 140 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired,
136 }).isRequired, 141 }).isRequired,
@@ -139,6 +144,7 @@ AppLayoutContainer.wrappedComponent.propTypes = {
139 setActive: PropTypes.func.isRequired, 144 setActive: PropTypes.func.isRequired,
140 reload: PropTypes.func.isRequired, 145 reload: PropTypes.func.isRequired,
141 toggleNotifications: PropTypes.func.isRequired, 146 toggleNotifications: PropTypes.func.isRequired,
147 toggleAudio: PropTypes.func.isRequired,
142 handleIPCMessage: PropTypes.func.isRequired, 148 handleIPCMessage: PropTypes.func.isRequired,
143 setWebviewReference: PropTypes.func.isRequired, 149 setWebviewReference: PropTypes.func.isRequired,
144 openWindow: PropTypes.func.isRequired, 150 openWindow: PropTypes.func.isRequired,
@@ -156,7 +162,7 @@ AppLayoutContainer.wrappedComponent.propTypes = {
156 }).isRequired, 162 }).isRequired,
157 app: PropTypes.shape({ 163 app: PropTypes.shape({
158 installUpdate: PropTypes.func.isRequired, 164 installUpdate: PropTypes.func.isRequired,
159 healthCheck: PropTypes.func.isRequired, 165 toggleMuteApp: PropTypes.func.isRequired,
160 }).isRequired, 166 }).isRequired,
161 requests: PropTypes.shape({ 167 requests: PropTypes.shape({
162 retryRequiredRequests: PropTypes.func.isRequired, 168 retryRequiredRequests: PropTypes.func.isRequired,
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index e298728d1..aa66d4bd0 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -61,6 +61,8 @@
61 "infobar.requiredRequestsFailed": "Could not load services and user information", 61 "infobar.requiredRequestsFailed": "Could not load services and user information",
62 "sidebar.settings": "Settings", 62 "sidebar.settings": "Settings",
63 "sidebar.addNewService": "Add new service", 63 "sidebar.addNewService": "Add new service",
64 "sidebar.mute": "Disable audio",
65 "sidebar.unmute": "Enable audio",
64 "services.welcome": "Welcome to Franz", 66 "services.welcome": "Welcome to Franz",
65 "services.getStarted": "Get started", 67 "services.getStarted": "Get started",
66 "settings.account.headline": "Account", 68 "settings.account.headline": "Account",
@@ -167,6 +169,8 @@
167 "tabs.item.edit": "Edit", 169 "tabs.item.edit": "Edit",
168 "tabs.item.disableNotifications": "Disable notifications", 170 "tabs.item.disableNotifications": "Disable notifications",
169 "tabs.item.enableNotification": "Enable notifications", 171 "tabs.item.enableNotification": "Enable notifications",
172 "tabs.item.disableAudio": "Disable audio",
173 "tabs.item.enableAudio": "Enable audio",
170 "tabs.item.disableService": "Disable service", 174 "tabs.item.disableService": "Disable service",
171 "tabs.item.deleteService": "Delete service", 175 "tabs.item.deleteService": "Delete service",
172 "service.crashHandler.headline": "Oh no!", 176 "service.crashHandler.headline": "Oh no!",
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index ecfd621d3..6580157d4 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -57,6 +57,8 @@ export default class AppStore extends Store {
57 this.actions.app.installUpdate.listen(this._installUpdate.bind(this)); 57 this.actions.app.installUpdate.listen(this._installUpdate.bind(this));
58 this.actions.app.resetUpdateStatus.listen(this._resetUpdateStatus.bind(this)); 58 this.actions.app.resetUpdateStatus.listen(this._resetUpdateStatus.bind(this));
59 this.actions.app.healthCheck.listen(this._healthCheck.bind(this)); 59 this.actions.app.healthCheck.listen(this._healthCheck.bind(this));
60 this.actions.app.muteApp.listen(this._muteApp.bind(this));
61 this.actions.app.toggleMuteApp.listen(this._toggleMuteApp.bind(this));
60 62
61 this.registerReactions([ 63 this.registerReactions([
62 this._offlineCheck.bind(this), 64 this._offlineCheck.bind(this),
@@ -202,6 +204,18 @@ export default class AppStore extends Store {
202 this.healthCheckRequest.execute(); 204 this.healthCheckRequest.execute();
203 } 205 }
204 206
207 @action _muteApp({ isMuted }) {
208 this.actions.settings.update({
209 settings: {
210 isMuted,
211 },
212 });
213 }
214
215 @action _toggleMuteApp() {
216 this._muteApp({ isMuted: !this.stores.settings.all.isMuted });
217 }
218
205 // Reactions 219 // Reactions
206 _offlineCheck() { 220 _offlineCheck() {
207 if (!this.isOnline) { 221 if (!this.isOnline) {
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 96c503510..a20718eca 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -48,6 +48,7 @@ export default class ServicesStore extends Store {
48 this.actions.service.reloadUpdatedServices.listen(this._reloadUpdatedServices.bind(this)); 48 this.actions.service.reloadUpdatedServices.listen(this._reloadUpdatedServices.bind(this));
49 this.actions.service.reorder.listen(this._reorder.bind(this)); 49 this.actions.service.reorder.listen(this._reorder.bind(this));
50 this.actions.service.toggleNotifications.listen(this._toggleNotifications.bind(this)); 50 this.actions.service.toggleNotifications.listen(this._toggleNotifications.bind(this));
51 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this));
51 this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); 52 this.actions.service.openDevTools.listen(this._openDevTools.bind(this));
52 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); 53 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this));
53 54
@@ -399,11 +400,25 @@ export default class ServicesStore extends Store {
399 @action _toggleNotifications({ serviceId }) { 400 @action _toggleNotifications({ serviceId }) {
400 const service = this.one(serviceId); 401 const service = this.one(serviceId);
401 402
403 this.actions.service.updateService({
404 serviceId,
405 serviceData: {
406 isNotificationEnabled: !service.isNotificationEnabled,
407 },
408 redirect: false,
409 });
410 }
411
412 @action _toggleAudio({ serviceId }) {
413 const service = this.one(serviceId);
414
402 service.isNotificationEnabled = !service.isNotificationEnabled; 415 service.isNotificationEnabled = !service.isNotificationEnabled;
403 416
404 this.actions.service.updateService({ 417 this.actions.service.updateService({
405 serviceId, 418 serviceId,
406 serviceData: service, 419 serviceData: {
420 isMuted: !service.isMuted,
421 },
407 redirect: false, 422 redirect: false,
408 }); 423 });
409 } 424 }
diff --git a/src/styles/layout.scss b/src/styles/layout.scss
index d87df2684..9f32bf2c4 100644
--- a/src/styles/layout.scss
+++ b/src/styles/layout.scss
@@ -42,6 +42,7 @@ html {
42 z-index: 200; 42 z-index: 200;
43 text-align: center; 43 text-align: center;
44 color: $theme-text-color; 44 color: $theme-text-color;
45 padding-bottom: 5px;
45 46
46 .sidebar__add-service { 47 .sidebar__add-service {
47 width: 32px; 48 width: 32px;
@@ -52,26 +53,27 @@ html {
52 color: $theme-gray-light; 53 color: $theme-gray-light;
53 } 54 }
54 55
55 .sidebar__settings-button { 56 .sidebar__button {
56 height: auto; 57 width: $theme-sidebar-width;
57 padding: 20px 0; 58 padding: 10px 0;
58 font-size: 12px; 59 font-size: 24px;
59 position: relative; 60 position: relative;
61 color: $theme-gray-light;
62 transition: color 0.25s, transform 0.25s;
60 63
61 .emoji { 64 &:hover {
62 position: absolute; 65 transform: scale(1.15);
63 top: 18px; 66 color: darken($theme-gray-light, 10%);
64 right: 12px; 67 }
65 68
66 img { 69 &:active {
67 width: 18px; 70 transition: transform 0.1s;
68 } 71 transform: scale(1);
69 } 72 }
70 }
71 73
72 .sidebar__logo { 74 &.is-muted {
73 width: 40px; 75 color: $theme-brand-primary;
74 height: auto; 76 }
75 } 77 }
76 78
77 & > div { 79 & > div {
diff --git a/src/styles/tabs.scss b/src/styles/tabs.scss
index 75568898b..abafdb53c 100644
--- a/src/styles/tabs.scss
+++ b/src/styles/tabs.scss
@@ -41,9 +41,16 @@
41 } 41 }
42 } 42 }
43 43
44 &:hover {
45 .tab-item__icon {
46 transform: scale(1.1);
47 }
48 }
49
44 .tab-item__icon { 50 .tab-item__icon {
45 width: 30px; 51 width: 30px;
46 height: auto; 52 height: auto;
53 transition: transform 0.25s;
47 } 54 }
48 55
49 .tab-item__message-count { 56 .tab-item__message-count {