diff options
Diffstat (limited to 'src')
33 files changed, 307 insertions, 57 deletions
diff --git a/src/actions/app.js b/src/actions/app.js index 25ff9344d..e4f648fc9 100644 --- a/src/actions/app.js +++ b/src/actions/app.js | |||
@@ -22,6 +22,7 @@ export default { | |||
22 | healthCheck: {}, | 22 | healthCheck: {}, |
23 | muteApp: { | 23 | muteApp: { |
24 | isMuted: PropTypes.bool.isRequired, | 24 | isMuted: PropTypes.bool.isRequired, |
25 | overrideSystemMute: PropTypes.bool, | ||
25 | }, | 26 | }, |
26 | toggleMuteApp: {}, | 27 | toggleMuteApp: {}, |
27 | }; | 28 | }; |
diff --git a/src/actions/user.js b/src/actions/user.js index fe32b8a05..ccf1fa56a 100644 --- a/src/actions/user.js +++ b/src/actions/user.js | |||
@@ -27,4 +27,5 @@ export default { | |||
27 | importLegacyServices: PropTypes.arrayOf(PropTypes.shape({ | 27 | importLegacyServices: PropTypes.arrayOf(PropTypes.shape({ |
28 | recipe: PropTypes.string.isRequired, | 28 | recipe: PropTypes.string.isRequired, |
29 | })).isRequired, | 29 | })).isRequired, |
30 | delete: {}, | ||
30 | }; | 31 | }; |
diff --git a/src/api/UserApi.js b/src/api/UserApi.js index e8fd75bed..edfb88988 100644 --- a/src/api/UserApi.js +++ b/src/api/UserApi.js | |||
@@ -46,4 +46,8 @@ export default class UserApi { | |||
46 | getLegacyServices() { | 46 | getLegacyServices() { |
47 | return this.server.getLegacyServices(); | 47 | return this.server.getLegacyServices(); |
48 | } | 48 | } |
49 | |||
50 | delete() { | ||
51 | return this.server.deleteAccount(); | ||
52 | } | ||
49 | } | 53 | } |
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index f25f02eaa..8b3136d27 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js | |||
@@ -12,6 +12,8 @@ import NewsModel from '../../models/News'; | |||
12 | import UserModel from '../../models/User'; | 12 | import UserModel from '../../models/User'; |
13 | import OrderModel from '../../models/Order'; | 13 | import OrderModel from '../../models/Order'; |
14 | 14 | ||
15 | import { sleep } from '../../helpers/async-helpers'; | ||
16 | |||
15 | import { API } from '../../environment'; | 17 | import { API } from '../../environment'; |
16 | 18 | ||
17 | import { | 19 | import { |
@@ -125,6 +127,19 @@ export default class ServerApi { | |||
125 | return user; | 127 | return user; |
126 | } | 128 | } |
127 | 129 | ||
130 | async deleteAccount() { | ||
131 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me`, this._prepareAuthRequest({ | ||
132 | method: 'DELETE', | ||
133 | })); | ||
134 | if (!request.ok) { | ||
135 | throw request; | ||
136 | } | ||
137 | const data = await request.json(); | ||
138 | |||
139 | console.debug('ServerApi::deleteAccount resolves', data); | ||
140 | return data; | ||
141 | } | ||
142 | |||
128 | // Services | 143 | // Services |
129 | async getServices() { | 144 | async getServices() { |
130 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/services`, this._prepareAuthRequest({ | 145 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/services`, this._prepareAuthRequest({ |
@@ -290,18 +305,25 @@ export default class ServerApi { | |||
290 | 305 | ||
291 | fs.ensureDirSync(recipeTempDirectory); | 306 | fs.ensureDirSync(recipeTempDirectory); |
292 | const res = await fetch(packageUrl); | 307 | const res = await fetch(packageUrl); |
308 | console.debug('Recipe downloaded', recipeId); | ||
293 | const buffer = await res.buffer(); | 309 | const buffer = await res.buffer(); |
294 | fs.writeFileSync(archivePath, buffer); | 310 | fs.writeFileSync(archivePath, buffer); |
295 | 311 | ||
296 | tar.x({ | 312 | await sleep(10); |
313 | |||
314 | await tar.x({ | ||
297 | file: archivePath, | 315 | file: archivePath, |
298 | cwd: recipeTempDirectory, | 316 | cwd: recipeTempDirectory, |
299 | sync: true, | 317 | preservePaths: true, |
318 | unlink: true, | ||
319 | preserveOwner: false, | ||
320 | onwarn: x => console.log('warn', recipeId, x), | ||
300 | }); | 321 | }); |
301 | 322 | ||
323 | await sleep(10); | ||
324 | |||
302 | const { id } = fs.readJsonSync(path.join(recipeTempDirectory, 'package.json')); | 325 | const { id } = fs.readJsonSync(path.join(recipeTempDirectory, 'package.json')); |
303 | const recipeDirectory = path.join(recipesDirectory, id); | 326 | const recipeDirectory = path.join(recipesDirectory, id); |
304 | |||
305 | fs.copySync(recipeTempDirectory, recipeDirectory); | 327 | fs.copySync(recipeTempDirectory, recipeDirectory); |
306 | fs.remove(recipeTempDirectory); | 328 | fs.remove(recipeTempDirectory); |
307 | fs.remove(path.join(recipesDirectory, recipeId, 'recipe.tar.gz')); | 329 | fs.remove(path.join(recipesDirectory, recipeId, 'recipe.tar.gz')); |
diff --git a/src/components/auth/Import.js b/src/components/auth/Import.js index 06493a0fd..078244434 100644 --- a/src/components/auth/Import.js +++ b/src/components/auth/Import.js | |||
@@ -24,7 +24,7 @@ const messages = defineMessages({ | |||
24 | }, | 24 | }, |
25 | skipButtonLabel: { | 25 | skipButtonLabel: { |
26 | id: 'import.skip.label', | 26 | id: 'import.skip.label', |
27 | defaultMessage: '!!!I want add services manually', | 27 | defaultMessage: '!!!I want to add services manually', |
28 | }, | 28 | }, |
29 | }); | 29 | }); |
30 | 30 | ||
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js index 06b10ecfe..eb9fbb847 100644 --- a/src/components/auth/Welcome.js +++ b/src/components/auth/Welcome.js | |||
@@ -55,12 +55,16 @@ export default class Login extends Component { | |||
55 | </div> | 55 | </div> |
56 | <div className="welcome__featured-services"> | 56 | <div className="welcome__featured-services"> |
57 | {recipes.map(recipe => ( | 57 | {recipes.map(recipe => ( |
58 | <img | 58 | <div |
59 | key={recipe.id} | 59 | key={recipe.id} |
60 | src={recipe.icons.svg} | ||
61 | className="welcome__featured-service" | 60 | className="welcome__featured-service" |
62 | alt="" | 61 | > |
63 | /> | 62 | <img |
63 | key={recipe.id} | ||
64 | src={recipe.icons.svg} | ||
65 | alt="" | ||
66 | /> | ||
67 | </div> | ||
64 | ))} | 68 | ))} |
65 | </div> | 69 | </div> |
66 | </div> | 70 | </div> |
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index cb2ecc8ce..915ebeace 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js | |||
@@ -17,12 +17,12 @@ const messages = defineMessages({ | |||
17 | defaultMessage: '!!!Add new service', | 17 | defaultMessage: '!!!Add new service', |
18 | }, | 18 | }, |
19 | mute: { | 19 | mute: { |
20 | id: 'sidebar.mute', | 20 | id: 'sidebar.muteApp', |
21 | defaultMessage: '!!!Disable audio', | 21 | defaultMessage: '!!!Disable notifications & audio', |
22 | }, | 22 | }, |
23 | unmute: { | 23 | unmute: { |
24 | id: 'sidebar.unmute', | 24 | id: 'sidebar.unmuteApp', |
25 | defaultMessage: '!!!Enable audio', | 25 | defaultMessage: '!!!Enable notifications & audio', |
26 | }, | 26 | }, |
27 | }); | 27 | }); |
28 | 28 | ||
diff --git a/src/components/services/tabs/TabBarSortableList.js b/src/components/services/tabs/TabBarSortableList.js index 2daf55676..489027d57 100644 --- a/src/components/services/tabs/TabBarSortableList.js +++ b/src/components/services/tabs/TabBarSortableList.js | |||
@@ -17,6 +17,8 @@ class TabBarSortableList extends Component { | |||
17 | deleteService: PropTypes.func.isRequired, | 17 | deleteService: PropTypes.func.isRequired, |
18 | disableService: PropTypes.func.isRequired, | 18 | disableService: PropTypes.func.isRequired, |
19 | enableService: PropTypes.func.isRequired, | 19 | enableService: PropTypes.func.isRequired, |
20 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, | ||
21 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, | ||
20 | } | 22 | } |
21 | 23 | ||
22 | render() { | 24 | render() { |
@@ -30,6 +32,8 @@ class TabBarSortableList extends Component { | |||
30 | disableService, | 32 | disableService, |
31 | enableService, | 33 | enableService, |
32 | openSettings, | 34 | openSettings, |
35 | showMessageBadgeWhenMutedSetting, | ||
36 | showMessageBadgesEvenWhenMuted, | ||
33 | } = this.props; | 37 | } = this.props; |
34 | 38 | ||
35 | return ( | 39 | return ( |
@@ -50,6 +54,8 @@ class TabBarSortableList extends Component { | |||
50 | disableService={() => disableService({ serviceId: service.id })} | 54 | disableService={() => disableService({ serviceId: service.id })} |
51 | enableService={() => enableService({ serviceId: service.id })} | 55 | enableService={() => enableService({ serviceId: service.id })} |
52 | openSettings={openSettings} | 56 | openSettings={openSettings} |
57 | showMessageBadgeWhenMutedSetting={showMessageBadgeWhenMutedSetting} | ||
58 | showMessageBadgesEvenWhenMuted={showMessageBadgesEvenWhenMuted} | ||
53 | /> | 59 | /> |
54 | ))} | 60 | ))} |
55 | {/* <li> | 61 | {/* <li> |
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js index a7136c43f..8403d9462 100644 --- a/src/components/services/tabs/TabItem.js +++ b/src/components/services/tabs/TabItem.js | |||
@@ -63,6 +63,8 @@ class TabItem extends Component { | |||
63 | deleteService: PropTypes.func.isRequired, | 63 | deleteService: PropTypes.func.isRequired, |
64 | disableService: PropTypes.func.isRequired, | 64 | disableService: PropTypes.func.isRequired, |
65 | enableService: PropTypes.func.isRequired, | 65 | enableService: PropTypes.func.isRequired, |
66 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, | ||
67 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, | ||
66 | }; | 68 | }; |
67 | 69 | ||
68 | static contextTypes = { | 70 | static contextTypes = { |
@@ -81,6 +83,8 @@ class TabItem extends Component { | |||
81 | disableService, | 83 | disableService, |
82 | enableService, | 84 | enableService, |
83 | openSettings, | 85 | openSettings, |
86 | showMessageBadgeWhenMutedSetting, | ||
87 | showMessageBadgesEvenWhenMuted, | ||
84 | } = this.props; | 88 | } = this.props; |
85 | const { intl } = this.context; | 89 | const { intl } = this.context; |
86 | 90 | ||
@@ -121,6 +125,26 @@ class TabItem extends Component { | |||
121 | }]; | 125 | }]; |
122 | const menu = Menu.buildFromTemplate(menuTemplate); | 126 | const menu = Menu.buildFromTemplate(menuTemplate); |
123 | 127 | ||
128 | let notificationBadge = null; | ||
129 | if ((showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) && showMessageBadgesEvenWhenMuted) { | ||
130 | notificationBadge = ( | ||
131 | <span> | ||
132 | {service.unreadDirectMessageCount > 0 && ( | ||
133 | <span className="tab-item__message-count"> | ||
134 | {service.unreadDirectMessageCount} | ||
135 | </span> | ||
136 | )} | ||
137 | {service.unreadIndirectMessageCount > 0 | ||
138 | && service.unreadDirectMessageCount === 0 | ||
139 | && service.isIndirectMessageBadgeEnabled && ( | ||
140 | <span className="tab-item__message-count is-indirect"> | ||
141 | • | ||
142 | </span> | ||
143 | )} | ||
144 | </span> | ||
145 | ); | ||
146 | } | ||
147 | |||
124 | return ( | 148 | return ( |
125 | <li | 149 | <li |
126 | className={classnames({ | 150 | className={classnames({ |
@@ -138,18 +162,7 @@ class TabItem extends Component { | |||
138 | className="tab-item__icon" | 162 | className="tab-item__icon" |
139 | alt="" | 163 | alt="" |
140 | /> | 164 | /> |
141 | {service.unreadDirectMessageCount > 0 && ( | 165 | {notificationBadge} |
142 | <span className="tab-item__message-count"> | ||
143 | {service.unreadDirectMessageCount} | ||
144 | </span> | ||
145 | )} | ||
146 | {service.unreadIndirectMessageCount > 0 | ||
147 | && service.unreadDirectMessageCount === 0 | ||
148 | && service.isIndirectMessageBadgeEnabled && ( | ||
149 | <span className="tab-item__message-count is-indirect"> | ||
150 | • | ||
151 | </span> | ||
152 | )} | ||
153 | </li> | 166 | </li> |
154 | ); | 167 | ); |
155 | } | 168 | } |
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js index 9da1090b7..ceb88c51c 100644 --- a/src/components/services/tabs/Tabbar.js +++ b/src/components/services/tabs/Tabbar.js | |||
@@ -18,6 +18,8 @@ export default class TabBar extends Component { | |||
18 | toggleAudio: PropTypes.func.isRequired, | 18 | toggleAudio: PropTypes.func.isRequired, |
19 | deleteService: PropTypes.func.isRequired, | 19 | deleteService: PropTypes.func.isRequired, |
20 | updateService: PropTypes.func.isRequired, | 20 | updateService: PropTypes.func.isRequired, |
21 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, | ||
22 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, | ||
21 | } | 23 | } |
22 | 24 | ||
23 | onSortEnd = ({ oldIndex, newIndex }) => { | 25 | onSortEnd = ({ oldIndex, newIndex }) => { |
@@ -64,6 +66,8 @@ export default class TabBar extends Component { | |||
64 | toggleNotifications, | 66 | toggleNotifications, |
65 | toggleAudio, | 67 | toggleAudio, |
66 | deleteService, | 68 | deleteService, |
69 | showMessageBadgeWhenMutedSetting, | ||
70 | showMessageBadgesEvenWhenMuted, | ||
67 | } = this.props; | 71 | } = this.props; |
68 | 72 | ||
69 | return ( | 73 | return ( |
@@ -85,6 +89,8 @@ export default class TabBar extends Component { | |||
85 | axis="y" | 89 | axis="y" |
86 | lockAxis="y" | 90 | lockAxis="y" |
87 | helperClass="is-reordering" | 91 | helperClass="is-reordering" |
92 | showMessageBadgeWhenMutedSetting={showMessageBadgeWhenMutedSetting} | ||
93 | showMessageBadgesEvenWhenMuted={showMessageBadgesEvenWhenMuted} | ||
88 | /> | 94 | /> |
89 | </div> | 95 | </div> |
90 | ); | 96 | ); |
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 75dbdef49..89fa07800 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js | |||
@@ -28,6 +28,10 @@ const messages = defineMessages({ | |||
28 | id: 'settings.account.headlineInvoices', | 28 | id: 'settings.account.headlineInvoices', |
29 | defaultMessage: '!!Invoices', | 29 | defaultMessage: '!!Invoices', |
30 | }, | 30 | }, |
31 | headlineDangerZone: { | ||
32 | id: 'settings.account.headlineDangerZone', | ||
33 | defaultMessage: '!!Danger Zone', | ||
34 | }, | ||
31 | manageSubscriptionButtonLabel: { | 35 | manageSubscriptionButtonLabel: { |
32 | id: 'settings.account.manageSubscription.label', | 36 | id: 'settings.account.manageSubscription.label', |
33 | defaultMessage: '!!!Manage your subscription', | 37 | defaultMessage: '!!!Manage your subscription', |
@@ -72,6 +76,18 @@ const messages = defineMessages({ | |||
72 | id: 'settings.account.mining.cancel', | 76 | id: 'settings.account.mining.cancel', |
73 | defaultMessage: '!!!Cancel mining', | 77 | defaultMessage: '!!!Cancel mining', |
74 | }, | 78 | }, |
79 | deleteAccount: { | ||
80 | id: 'settings.account.deleteAccount', | ||
81 | defaultMessage: '!!!Delete account', | ||
82 | }, | ||
83 | deleteInfo: { | ||
84 | id: 'settings.account.deleteInfo', | ||
85 | defaultMessage: '!!!If you don\'t need your Franz account any longer, you can delete your account and all related data here.', | ||
86 | }, | ||
87 | deleteEmailSent: { | ||
88 | id: 'settings.account.deleteEmailSent', | ||
89 | defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', | ||
90 | }, | ||
75 | }); | 91 | }); |
76 | 92 | ||
77 | @observer | 93 | @observer |
@@ -90,6 +106,9 @@ export default class AccountDashboard extends Component { | |||
90 | openExternalUrl: PropTypes.func.isRequired, | 106 | openExternalUrl: PropTypes.func.isRequired, |
91 | onCloseSubscriptionWindow: PropTypes.func.isRequired, | 107 | onCloseSubscriptionWindow: PropTypes.func.isRequired, |
92 | stopMiner: PropTypes.func.isRequired, | 108 | stopMiner: PropTypes.func.isRequired, |
109 | deleteAccount: PropTypes.func.isRequired, | ||
110 | isLoadingDeleteAccount: PropTypes.bool.isRequired, | ||
111 | isDeleteAccountSuccessful: PropTypes.bool.isRequired, | ||
93 | }; | 112 | }; |
94 | 113 | ||
95 | static contextTypes = { | 114 | static contextTypes = { |
@@ -111,6 +130,9 @@ export default class AccountDashboard extends Component { | |||
111 | retryUserInfoRequest, | 130 | retryUserInfoRequest, |
112 | onCloseSubscriptionWindow, | 131 | onCloseSubscriptionWindow, |
113 | stopMiner, | 132 | stopMiner, |
133 | deleteAccount, | ||
134 | isLoadingDeleteAccount, | ||
135 | isDeleteAccountSuccessful, | ||
114 | } = this.props; | 136 | } = this.props; |
115 | const { intl } = this.context; | 137 | const { intl } = this.context; |
116 | 138 | ||
@@ -201,7 +223,7 @@ export default class AccountDashboard extends Component { | |||
201 | /> | 223 | /> |
202 | </div> | 224 | </div> |
203 | </div> | 225 | </div> |
204 | <div className="account__box account__box--last"> | 226 | <div className="account__box"> |
205 | <h2>{intl.formatMessage(messages.headlineInvoices)}</h2> | 227 | <h2>{intl.formatMessage(messages.headlineInvoices)}</h2> |
206 | <table className="invoices"> | 228 | <table className="invoices"> |
207 | <tbody> | 229 | <tbody> |
@@ -230,7 +252,7 @@ export default class AccountDashboard extends Component { | |||
230 | 252 | ||
231 | {user.isMiner && ( | 253 | {user.isMiner && ( |
232 | <div className="account franz-form"> | 254 | <div className="account franz-form"> |
233 | <div className="account__box"> | 255 | <div className="account__box account__box--last"> |
234 | <h2>{intl.formatMessage(messages.headlineSubscription)}</h2> | 256 | <h2>{intl.formatMessage(messages.headlineSubscription)}</h2> |
235 | <div className="account__subscription"> | 257 | <div className="account__subscription"> |
236 | <div> | 258 | <div> |
@@ -267,7 +289,7 @@ export default class AccountDashboard extends Component { | |||
267 | <Loader /> | 289 | <Loader /> |
268 | ) : ( | 290 | ) : ( |
269 | <div className="account franz-form"> | 291 | <div className="account franz-form"> |
270 | <div className="account__box account__box--last"> | 292 | <div className="account__box"> |
271 | <h2>{intl.formatMessage(messages.headlineUpgrade)}</h2> | 293 | <h2>{intl.formatMessage(messages.headlineUpgrade)}</h2> |
272 | <SubscriptionForm | 294 | <SubscriptionForm |
273 | onCloseWindow={onCloseSubscriptionWindow} | 295 | onCloseWindow={onCloseSubscriptionWindow} |
@@ -276,8 +298,29 @@ export default class AccountDashboard extends Component { | |||
276 | </div> | 298 | </div> |
277 | ) | 299 | ) |
278 | )} | 300 | )} |
301 | |||
302 | <div className="account franz-form"> | ||
303 | <div className="account__box"> | ||
304 | <h2>{intl.formatMessage(messages.headlineDangerZone)}</h2> | ||
305 | {!isDeleteAccountSuccessful && ( | ||
306 | <div className="account__subscription"> | ||
307 | <p>{intl.formatMessage(messages.deleteInfo)}</p> | ||
308 | <Button | ||
309 | label={intl.formatMessage(messages.deleteAccount)} | ||
310 | buttonType="danger" | ||
311 | onClick={() => deleteAccount()} | ||
312 | loaded={!isLoadingDeleteAccount} | ||
313 | /> | ||
314 | </div> | ||
315 | )} | ||
316 | {isDeleteAccountSuccessful && ( | ||
317 | <p>{intl.formatMessage(messages.deleteEmailSent)}</p> | ||
318 | )} | ||
319 | </div> | ||
320 | </div> | ||
279 | </div> | 321 | </div> |
280 | )} | 322 | )} |
323 | |||
281 | </div> | 324 | </div> |
282 | <ReactTooltip place="right" type="dark" effect="solid" /> | 325 | <ReactTooltip place="right" type="dark" effect="solid" /> |
283 | </div> | 326 | </div> |
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js index 4ce9b7ab2..878e46d6d 100644 --- a/src/components/settings/settings/EditSettingsForm.js +++ b/src/components/settings/settings/EditSettingsForm.js | |||
@@ -142,6 +142,7 @@ export default class EditSettingsForm extends Component { | |||
142 | {/* Appearance */} | 142 | {/* Appearance */} |
143 | <h2 id="apperance">{intl.formatMessage(messages.headlineAppearance)}</h2> | 143 | <h2 id="apperance">{intl.formatMessage(messages.headlineAppearance)}</h2> |
144 | <Toggle field={form.$('showDisabledServices')} /> | 144 | <Toggle field={form.$('showDisabledServices')} /> |
145 | <Toggle field={form.$('showMessageBadgeWhenMuted')} /> | ||
145 | 146 | ||
146 | {/* Language */} | 147 | {/* Language */} |
147 | <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2> | 148 | <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2> |
diff --git a/src/components/ui/Button.js b/src/components/ui/Button.js index 07e94192f..554206cb7 100644 --- a/src/components/ui/Button.js +++ b/src/components/ui/Button.js | |||
@@ -68,7 +68,7 @@ export default class Button extends Component { | |||
68 | loaded={loaded} | 68 | loaded={loaded} |
69 | lines={10} | 69 | lines={10} |
70 | scale={0.4} | 70 | scale={0.4} |
71 | color={buttonType === '' ? '#FFF' : '#373a3c'} | 71 | color={buttonType !== 'secondary' ? '#FFF' : '#373a3c'} |
72 | component="span" | 72 | component="span" |
73 | /> | 73 | /> |
74 | {label} | 74 | {label} |
diff --git a/src/config.js b/src/config.js index b3e00c92c..e6d8958e6 100644 --- a/src/config.js +++ b/src/config.js | |||
@@ -11,6 +11,7 @@ export const DEFAULT_APP_SETTINGS = { | |||
11 | enableSystemTray: true, | 11 | enableSystemTray: true, |
12 | minimizeToSystemTray: false, | 12 | minimizeToSystemTray: false, |
13 | showDisabledServices: true, | 13 | showDisabledServices: true, |
14 | showMessageBadgeWhenMuted: true, | ||
14 | enableSpellchecking: true, | 15 | enableSpellchecking: true, |
15 | // spellcheckingLanguage: 'auto', | 16 | // spellcheckingLanguage: 'auto', |
16 | locale: 'en-US', | 17 | locale: 'en-US', |
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 7c6ceccd6..e4a9d60c3 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js | |||
@@ -73,13 +73,11 @@ export default class AppLayoutContainer extends Component { | |||
73 | ); | 73 | ); |
74 | } | 74 | } |
75 | 75 | ||
76 | const isMuted = settings.all.isAppMuted || app.isSystemMuted; | ||
77 | |||
78 | const sidebar = ( | 76 | const sidebar = ( |
79 | <Sidebar | 77 | <Sidebar |
80 | services={services.allDisplayed} | 78 | services={services.allDisplayed} |
81 | setActive={setActive} | 79 | setActive={setActive} |
82 | isAppMuted={isMuted} | 80 | isAppMuted={settings.all.isAppMuted} |
83 | openSettings={openSettings} | 81 | openSettings={openSettings} |
84 | closeSettings={closeSettings} | 82 | closeSettings={closeSettings} |
85 | reorder={reorder} | 83 | reorder={reorder} |
@@ -89,6 +87,8 @@ export default class AppLayoutContainer extends Component { | |||
89 | deleteService={deleteService} | 87 | deleteService={deleteService} |
90 | updateService={updateService} | 88 | updateService={updateService} |
91 | toggleMuteApp={toggleMuteApp} | 89 | toggleMuteApp={toggleMuteApp} |
90 | showMessageBadgeWhenMutedSetting={settings.all.showMessageBadgeWhenMuted} | ||
91 | showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted} | ||
92 | /> | 92 | /> |
93 | ); | 93 | ); |
94 | 94 | ||
@@ -99,7 +99,7 @@ export default class AppLayoutContainer extends Component { | |||
99 | setWebviewReference={setWebviewReference} | 99 | setWebviewReference={setWebviewReference} |
100 | openWindow={openWindow} | 100 | openWindow={openWindow} |
101 | reload={reload} | 101 | reload={reload} |
102 | isAppMuted={isMuted} | 102 | isAppMuted={settings.all.isAppMuted} |
103 | update={updateService} | 103 | update={updateService} |
104 | /> | 104 | /> |
105 | ); | 105 | ); |
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js index a1ac8bda3..008c495d4 100644 --- a/src/containers/settings/AccountScreen.js +++ b/src/containers/settings/AccountScreen.js | |||
@@ -69,6 +69,7 @@ export default class AccountScreen extends Component { | |||
69 | render() { | 69 | render() { |
70 | const { user, payment, app } = this.props.stores; | 70 | const { user, payment, app } = this.props.stores; |
71 | const { openExternalUrl } = this.props.actions.app; | 71 | const { openExternalUrl } = this.props.actions.app; |
72 | const { user: userActions } = this.props.actions; | ||
72 | 73 | ||
73 | const isLoadingUserInfo = user.getUserInfoRequest.isExecuting; | 74 | const isLoadingUserInfo = user.getUserInfoRequest.isExecuting; |
74 | const isLoadingOrdersInfo = payment.ordersDataRequest.isExecuting; | 75 | const isLoadingOrdersInfo = payment.ordersDataRequest.isExecuting; |
@@ -89,6 +90,9 @@ export default class AccountScreen extends Component { | |||
89 | openExternalUrl={url => openExternalUrl({ url })} | 90 | openExternalUrl={url => openExternalUrl({ url })} |
90 | onCloseSubscriptionWindow={() => this.onCloseWindow()} | 91 | onCloseSubscriptionWindow={() => this.onCloseWindow()} |
91 | stopMiner={() => this.stopMiner()} | 92 | stopMiner={() => this.stopMiner()} |
93 | deleteAccount={userActions.delete} | ||
94 | isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} | ||
95 | isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} | ||
92 | /> | 96 | /> |
93 | ); | 97 | ); |
94 | } | 98 | } |
@@ -109,6 +113,7 @@ AccountScreen.wrappedComponent.propTypes = { | |||
109 | }).isRequired, | 113 | }).isRequired, |
110 | user: PropTypes.shape({ | 114 | user: PropTypes.shape({ |
111 | update: PropTypes.func.isRequired, | 115 | update: PropTypes.func.isRequired, |
116 | delete: PropTypes.func.isRequired, | ||
112 | }).isRequired, | 117 | }).isRequired, |
113 | }).isRequired, | 118 | }).isRequired, |
114 | }; | 119 | }; |
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js index 62e255dab..45ded9e5c 100644 --- a/src/containers/settings/EditSettingsScreen.js +++ b/src/containers/settings/EditSettingsScreen.js | |||
@@ -43,6 +43,10 @@ const messages = defineMessages({ | |||
43 | id: 'settings.app.form.showDisabledServices', | 43 | id: 'settings.app.form.showDisabledServices', |
44 | defaultMessage: '!!!Display disabled services tabs', | 44 | defaultMessage: '!!!Display disabled services tabs', |
45 | }, | 45 | }, |
46 | showMessageBadgeWhenMuted: { | ||
47 | id: 'settings.app.form.showMessagesBadgesWhenMuted', | ||
48 | defaultMessage: '!!!Show unread message badge when notifications are disabled', | ||
49 | }, | ||
46 | enableSpellchecking: { | 50 | enableSpellchecking: { |
47 | id: 'settings.app.form.enableSpellchecking', | 51 | id: 'settings.app.form.enableSpellchecking', |
48 | defaultMessage: '!!!Enable spell checking', | 52 | defaultMessage: '!!!Enable spell checking', |
@@ -85,6 +89,7 @@ export default class EditSettingsScreen extends Component { | |||
85 | enableSystemTray: settingsData.enableSystemTray, | 89 | enableSystemTray: settingsData.enableSystemTray, |
86 | minimizeToSystemTray: settingsData.minimizeToSystemTray, | 90 | minimizeToSystemTray: settingsData.minimizeToSystemTray, |
87 | showDisabledServices: settingsData.showDisabledServices, | 91 | showDisabledServices: settingsData.showDisabledServices, |
92 | showMessageBadgeWhenMuted: settingsData.showMessageBadgeWhenMuted, | ||
88 | enableSpellchecking: settingsData.enableSpellchecking, | 93 | enableSpellchecking: settingsData.enableSpellchecking, |
89 | // spellcheckingLanguage: settingsData.spellcheckingLanguage, | 94 | // spellcheckingLanguage: settingsData.spellcheckingLanguage, |
90 | locale: settingsData.locale, | 95 | locale: settingsData.locale, |
@@ -154,6 +159,11 @@ export default class EditSettingsScreen extends Component { | |||
154 | value: settings.all.showDisabledServices, | 159 | value: settings.all.showDisabledServices, |
155 | default: DEFAULT_APP_SETTINGS.showDisabledServices, | 160 | default: DEFAULT_APP_SETTINGS.showDisabledServices, |
156 | }, | 161 | }, |
162 | showMessageBadgeWhenMuted: { | ||
163 | label: intl.formatMessage(messages.showMessageBadgeWhenMuted), | ||
164 | value: settings.all.showMessageBadgeWhenMuted, | ||
165 | default: DEFAULT_APP_SETTINGS.showMessageBadgeWhenMuted, | ||
166 | }, | ||
157 | enableSpellchecking: { | 167 | enableSpellchecking: { |
158 | label: intl.formatMessage(messages.enableSpellchecking), | 168 | label: intl.formatMessage(messages.enableSpellchecking), |
159 | value: settings.all.enableSpellchecking, | 169 | value: settings.all.enableSpellchecking, |
diff --git a/src/electron/deepLinking.js b/src/electron/deepLinking.js new file mode 100644 index 000000000..16e68b914 --- /dev/null +++ b/src/electron/deepLinking.js | |||
@@ -0,0 +1,5 @@ | |||
1 | export default function handleDeepLink(window, rawUrl) { | ||
2 | const url = rawUrl.replace('franz://', ''); | ||
3 | |||
4 | window.webContents.send('navigateFromDeepLink', { url }); | ||
5 | } | ||
diff --git a/src/helpers/async-helpers.js b/src/helpers/async-helpers.js new file mode 100644 index 000000000..2ef01ee09 --- /dev/null +++ b/src/helpers/async-helpers.js | |||
@@ -0,0 +1,5 @@ | |||
1 | /* eslint-disable import/prefer-default-export */ | ||
2 | |||
3 | export function sleep(ms = 0) { | ||
4 | return new Promise(r => setTimeout(r, ms)); | ||
5 | } | ||
diff --git a/src/i18n/locales/el.json b/src/i18n/locales/el.json index 5717a18b1..459d097f3 100644 --- a/src/i18n/locales/el.json +++ b/src/i18n/locales/el.json | |||
@@ -3,7 +3,7 @@ | |||
3 | "global.notConnectedToTheInternet" : "You are not connected to the internet.", | 3 | "global.notConnectedToTheInternet" : "You are not connected to the internet.", |
4 | "import.headline" : "Import your Franz 4 services", | 4 | "import.headline" : "Import your Franz 4 services", |
5 | "import.notSupportedHeadline" : "Services not yet supported in Franz 5", | 5 | "import.notSupportedHeadline" : "Services not yet supported in Franz 5", |
6 | "import.skip.label" : "I want add services manually", | 6 | "import.skip.label" : "I want to add services manually", |
7 | "import.submit.label" : "Import services", | 7 | "import.submit.label" : "Import services", |
8 | "infobar.buttonChangelog" : "What is new?", | 8 | "infobar.buttonChangelog" : "What is new?", |
9 | "infobar.buttonInstallUpdate" : "Restart & install update", | 9 | "infobar.buttonInstallUpdate" : "Restart & install update", |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 8de5e5e02..48b408e59 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -39,7 +39,7 @@ | |||
39 | "import.headline": "Import your Franz 4 services", | 39 | "import.headline": "Import your Franz 4 services", |
40 | "import.notSupportedHeadline": "Services not yet supported in Franz 5", | 40 | "import.notSupportedHeadline": "Services not yet supported in Franz 5", |
41 | "import.submit.label": "Import services", | 41 | "import.submit.label": "Import services", |
42 | "import.skip.label": "I want add services manually", | 42 | "import.skip.label": "I want to add services manually", |
43 | "invite.submit.label": "Send invites", | 43 | "invite.submit.label": "Send invites", |
44 | "invite.headline.friends": "Invite 3 of your friends or colleagues", | 44 | "invite.headline.friends": "Invite 3 of your friends or colleagues", |
45 | "invite.name.label": "Name", | 45 | "invite.name.label": "Name", |
@@ -62,14 +62,15 @@ | |||
62 | "infobar.requiredRequestsFailed": "Could not load services and user information", | 62 | "infobar.requiredRequestsFailed": "Could not load services and user information", |
63 | "sidebar.settings": "Settings", | 63 | "sidebar.settings": "Settings", |
64 | "sidebar.addNewService": "Add new service", | 64 | "sidebar.addNewService": "Add new service", |
65 | "sidebar.mute": "Disable audio", | 65 | "sidebar.muteApp": "Disable notifications & audio", |
66 | "sidebar.unmute": "Enable audio", | 66 | "sidebar.unmuteApp": "Enable notifications & audio", |
67 | "services.welcome": "Welcome to Franz", | 67 | "services.welcome": "Welcome to Franz", |
68 | "services.getStarted": "Get started", | 68 | "services.getStarted": "Get started", |
69 | "settings.account.headline": "Account", | 69 | "settings.account.headline": "Account", |
70 | "settings.account.headlineSubscription": "Your subscription", | 70 | "settings.account.headlineSubscription": "Your subscription", |
71 | "settings.account.headlineUpgrade": "Upgrade your account & support Franz", | 71 | "settings.account.headlineUpgrade": "Upgrade your account & support Franz", |
72 | "settings.account.headlineInvoices": "Invoices", | 72 | "settings.account.headlineInvoices": "Invoices", |
73 | "settings.account.headlineDangerZone": "Danger Zone", | ||
73 | "settings.account.manageSubscription.label": "Manage your subscription", | 74 | "settings.account.manageSubscription.label": "Manage your subscription", |
74 | "settings.account.accountType.basic": "Basic Account", | 75 | "settings.account.accountType.basic": "Basic Account", |
75 | "settings.account.accountType.premium": "Premium Supporter Account", | 76 | "settings.account.accountType.premium": "Premium Supporter Account", |
@@ -86,6 +87,9 @@ | |||
86 | "settings.account.mining.active": "You are right now performing {hashes} calculations per second.", | 87 | "settings.account.mining.active": "You are right now performing {hashes} calculations per second.", |
87 | "settings.account.mining.moreInformation": "Get more information", | 88 | "settings.account.mining.moreInformation": "Get more information", |
88 | "settings.account.mining.cancel": "Cancel mining", | 89 | "settings.account.mining.cancel": "Cancel mining", |
90 | "settings.account.deleteAccount": "Delete account", | ||
91 | "settings.account.deleteInfo": "If you don't need your Franz account any longer, you can delete your account and all related data here.", | ||
92 | "settings.account.deleteEmailSent": "You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", | ||
89 | "settings.navigation.availableServices": "Available services", | 93 | "settings.navigation.availableServices": "Available services", |
90 | "settings.navigation.yourServices": "Your services", | 94 | "settings.navigation.yourServices": "Your services", |
91 | "settings.navigation.account": "Account", | 95 | "settings.navigation.account": "Account", |
@@ -148,6 +152,7 @@ | |||
148 | "settings.app.form.language": "Language", | 152 | "settings.app.form.language": "Language", |
149 | "settings.app.form.enableSpellchecking": "Enable spell checking", | 153 | "settings.app.form.enableSpellchecking": "Enable spell checking", |
150 | "settings.app.form.showDisabledServices": "Display disabled services tabs", | 154 | "settings.app.form.showDisabledServices": "Display disabled services tabs", |
155 | "settings.app.form.showMessagesBadgesWhenMuted": "Show unread message badge when notifications are disabled", | ||
151 | "settings.app.form.beta": "Include beta versions", | 156 | "settings.app.form.beta": "Include beta versions", |
152 | "settings.app.translationHelp": "Help us to translate Franz into your language.", | 157 | "settings.app.translationHelp": "Help us to translate Franz into your language.", |
153 | "settings.app.currentVersion": "Current version:", | 158 | "settings.app.currentVersion": "Current version:", |
@@ -188,5 +193,5 @@ | |||
188 | "service.crashHandler.action": "Reload {name}", | 193 | "service.crashHandler.action": "Reload {name}", |
189 | "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", | 194 | "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", |
190 | "service.disabledHandler.headline": "{name} is disabled", | 195 | "service.disabledHandler.headline": "{name} is disabled", |
191 | "service.disabledHandler.action": "Enable {name}" | 196 | "service.disabledHandler.action": "Enable {name}" |
192 | } | 197 | } |
diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 5717a18b1..459d097f3 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json | |||
@@ -3,7 +3,7 @@ | |||
3 | "global.notConnectedToTheInternet" : "You are not connected to the internet.", | 3 | "global.notConnectedToTheInternet" : "You are not connected to the internet.", |
4 | "import.headline" : "Import your Franz 4 services", | 4 | "import.headline" : "Import your Franz 4 services", |
5 | "import.notSupportedHeadline" : "Services not yet supported in Franz 5", | 5 | "import.notSupportedHeadline" : "Services not yet supported in Franz 5", |
6 | "import.skip.label" : "I want add services manually", | 6 | "import.skip.label" : "I want to add services manually", |
7 | "import.submit.label" : "Import services", | 7 | "import.submit.label" : "Import services", |
8 | "infobar.buttonChangelog" : "What is new?", | 8 | "infobar.buttonChangelog" : "What is new?", |
9 | "infobar.buttonInstallUpdate" : "Restart & install update", | 9 | "infobar.buttonInstallUpdate" : "Restart & install update", |
diff --git a/src/index.js b/src/index.js index 6a08e5e5a..4253b681f 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -8,6 +8,7 @@ import { isDevMode, isWindows } from './environment'; | |||
8 | import ipcApi from './electron/ipc-api'; | 8 | import ipcApi from './electron/ipc-api'; |
9 | import Tray from './lib/Tray'; | 9 | import Tray from './lib/Tray'; |
10 | import Settings from './electron/Settings'; | 10 | import Settings from './electron/Settings'; |
11 | import handleDeepLink from './electron/deepLinking'; | ||
11 | import { appId } from './package.json'; // eslint-disable-line import/no-unresolved | 12 | import { appId } from './package.json'; // eslint-disable-line import/no-unresolved |
12 | import './electron/exception'; | 13 | import './electron/exception'; |
13 | 14 | ||
@@ -26,10 +27,20 @@ if (isWindows) { | |||
26 | } | 27 | } |
27 | 28 | ||
28 | // Force single window | 29 | // Force single window |
29 | const isSecondInstance = app.makeSingleInstance(() => { | 30 | const isSecondInstance = app.makeSingleInstance((argv) => { |
30 | if (mainWindow) { | 31 | if (mainWindow) { |
31 | if (mainWindow.isMinimized()) mainWindow.restore(); | 32 | if (mainWindow.isMinimized()) mainWindow.restore(); |
32 | mainWindow.focus(); | 33 | mainWindow.focus(); |
34 | |||
35 | if (process.platform === 'win32') { | ||
36 | // Keep only command line / deep linked arguments | ||
37 | const url = argv.slice(1); | ||
38 | |||
39 | if (url) { | ||
40 | console.log(url.toString()); | ||
41 | handleDeepLink(mainWindow, url.toString()); | ||
42 | } | ||
43 | } | ||
33 | } | 44 | } |
34 | }); | 45 | }); |
35 | 46 | ||
@@ -70,7 +81,8 @@ const createWindow = () => { | |||
70 | const trayIcon = new Tray(); | 81 | const trayIcon = new Tray(); |
71 | 82 | ||
72 | // Initialize ipcApi | 83 | // Initialize ipcApi |
73 | ipcApi({ mainWindow, settings, trayIcon }); | 84 | const franzIpcApi = ipcApi({ mainWindow, settings, trayIcon }); |
85 | console.log(franzIpcApi); | ||
74 | 86 | ||
75 | // Manage Window State | 87 | // Manage Window State |
76 | mainWindowState.manage(mainWindow); | 88 | mainWindowState.manage(mainWindow); |
@@ -137,6 +149,8 @@ const createWindow = () => { | |||
137 | 149 | ||
138 | mainWindow.on('show', () => { | 150 | mainWindow.on('show', () => { |
139 | mainWindow.setSkipTaskbar(false); | 151 | mainWindow.setSkipTaskbar(false); |
152 | |||
153 | handleDeepLink(mainWindow, 'franz://settings/services/add/msteams'); | ||
140 | }); | 154 | }); |
141 | 155 | ||
142 | app.mainWindow = mainWindow; | 156 | app.mainWindow = mainWindow; |
@@ -176,3 +190,15 @@ app.on('activate', () => { | |||
176 | mainWindow.show(); | 190 | mainWindow.show(); |
177 | } | 191 | } |
178 | }); | 192 | }); |
193 | |||
194 | app.on('will-finish-launching', () => { | ||
195 | // Protocol handler for osx | ||
196 | app.on('open-url', (event, url) => { | ||
197 | event.preventDefault(); | ||
198 | console.log(`open-url event: ${url}`); | ||
199 | handleDeepLink(mainWindow, url); | ||
200 | }); | ||
201 | }); | ||
202 | |||
203 | // Register App URL | ||
204 | app.setAsDefaultProtocolClient('franz'); | ||
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 6624ab75e..d9c30466b 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -249,7 +249,7 @@ export default class FranzMenu { | |||
249 | } | 249 | } |
250 | 250 | ||
251 | @computed get serviceTpl() { | 251 | @computed get serviceTpl() { |
252 | const services = this.stores.services.enabled; | 252 | const services = this.stores.services.allDisplayed; |
253 | 253 | ||
254 | if (this.stores.user.isLoggedIn) { | 254 | if (this.stores.user.isLoggedIn) { |
255 | return services.map((service, i) => ({ | 255 | return services.map((service, i) => ({ |
diff --git a/src/models/Settings.js b/src/models/Settings.js index 3b352f9aa..35bfe0d05 100644 --- a/src/models/Settings.js +++ b/src/models/Settings.js | |||
@@ -8,6 +8,7 @@ export default class Settings { | |||
8 | @observable enableSystemTray = DEFAULT_APP_SETTINGS.enableSystemTray; | 8 | @observable enableSystemTray = DEFAULT_APP_SETTINGS.enableSystemTray; |
9 | @observable minimizeToSystemTray = DEFAULT_APP_SETTINGS.minimizeToSystemTray; | 9 | @observable minimizeToSystemTray = DEFAULT_APP_SETTINGS.minimizeToSystemTray; |
10 | @observable showDisabledServices = DEFAULT_APP_SETTINGS.showDisabledServices; | 10 | @observable showDisabledServices = DEFAULT_APP_SETTINGS.showDisabledServices; |
11 | @observable showMessageBadgeWhenMuted = DEFAULT_APP_SETTINGS.showMessageBadgeWhenMuted; | ||
11 | @observable enableSpellchecking = DEFAULT_APP_SETTINGS.enableSpellchecking; | 12 | @observable enableSpellchecking = DEFAULT_APP_SETTINGS.enableSpellchecking; |
12 | @observable locale = DEFAULT_APP_SETTINGS.locale; | 13 | @observable locale = DEFAULT_APP_SETTINGS.locale; |
13 | @observable beta = DEFAULT_APP_SETTINGS.beta; | 14 | @observable beta = DEFAULT_APP_SETTINGS.beta; |
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index 0b7c60bce..3e6d4d288 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -45,7 +45,7 @@ export default class AppStore extends Store { | |||
45 | miner = null; | 45 | miner = null; |
46 | @observable minerHashrate = 0.0; | 46 | @observable minerHashrate = 0.0; |
47 | 47 | ||
48 | @observable isSystemMuted = false; | 48 | @observable isSystemMuteOverridden = false; |
49 | 49 | ||
50 | constructor(...args) { | 50 | constructor(...args) { |
51 | super(...args); | 51 | super(...args); |
@@ -67,6 +67,7 @@ export default class AppStore extends Store { | |||
67 | this._setLocale.bind(this), | 67 | this._setLocale.bind(this), |
68 | this._handleMiner.bind(this), | 68 | this._handleMiner.bind(this), |
69 | this._handleMinerThrottle.bind(this), | 69 | this._handleMinerThrottle.bind(this), |
70 | this._muteAppHandler.bind(this), | ||
70 | ]); | 71 | ]); |
71 | } | 72 | } |
72 | 73 | ||
@@ -115,6 +116,14 @@ export default class AppStore extends Store { | |||
115 | } | 116 | } |
116 | }); | 117 | }); |
117 | 118 | ||
119 | // Handle deep linking (franz://) | ||
120 | ipcRenderer.on('navigateFromDeepLink', (event, data) => { | ||
121 | const { url } = data; | ||
122 | if (!url) return; | ||
123 | |||
124 | this.stores.router.push(data.url); | ||
125 | }); | ||
126 | |||
118 | // Check system idle time every minute | 127 | // Check system idle time every minute |
119 | setInterval(() => { | 128 | setInterval(() => { |
120 | this.idleTime = idleTimer.getIdleTime(); | 129 | this.idleTime = idleTimer.getIdleTime(); |
@@ -137,7 +146,7 @@ export default class AppStore extends Store { | |||
137 | this.actions.service.setActivePrev(); | 146 | this.actions.service.setActivePrev(); |
138 | }); | 147 | }); |
139 | 148 | ||
140 | // Global Mute | 149 | // Global Mute |
141 | key( | 150 | key( |
142 | '⌘+shift+m ctrl+shift+m', () => { | 151 | '⌘+shift+m ctrl+shift+m', () => { |
143 | this.actions.app.toggleMuteApp(); | 152 | this.actions.app.toggleMuteApp(); |
@@ -150,6 +159,8 @@ export default class AppStore extends Store { | |||
150 | 159 | ||
151 | // Actions | 160 | // Actions |
152 | @action _notify({ title, options, notificationId, serviceId = null }) { | 161 | @action _notify({ title, options, notificationId, serviceId = null }) { |
162 | if (this.stores.settings.all.isAppMuted) return; | ||
163 | |||
153 | const notification = new window.Notification(title, options); | 164 | const notification = new window.Notification(title, options); |
154 | notification.onclick = (e) => { | 165 | notification.onclick = (e) => { |
155 | if (serviceId) { | 166 | if (serviceId) { |
@@ -160,6 +171,11 @@ export default class AppStore extends Store { | |||
160 | }); | 171 | }); |
161 | 172 | ||
162 | this.actions.service.setActive({ serviceId }); | 173 | this.actions.service.setActive({ serviceId }); |
174 | |||
175 | if (!isMac) { | ||
176 | const mainWindow = remote.getCurrentWindow(); | ||
177 | mainWindow.restore(); | ||
178 | } | ||
163 | } | 179 | } |
164 | }; | 180 | }; |
165 | } | 181 | } |
@@ -217,7 +233,9 @@ export default class AppStore extends Store { | |||
217 | this.healthCheckRequest.execute(); | 233 | this.healthCheckRequest.execute(); |
218 | } | 234 | } |
219 | 235 | ||
220 | @action _muteApp({ isMuted }) { | 236 | @action _muteApp({ isMuted, overrideSystemMute = true }) { |
237 | this.isSystemMuteOverriden = overrideSystemMute; | ||
238 | |||
221 | this.actions.settings.update({ | 239 | this.actions.settings.update({ |
222 | settings: { | 240 | settings: { |
223 | isAppMuted: isMuted, | 241 | isAppMuted: isMuted, |
@@ -296,6 +314,14 @@ export default class AppStore extends Store { | |||
296 | } | 314 | } |
297 | } | 315 | } |
298 | 316 | ||
317 | _muteAppHandler() { | ||
318 | const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; | ||
319 | |||
320 | if (!showMessageBadgesEvenWhenMuted) { | ||
321 | this.actions.app.setBadge({ unreadDirectMessageCount: 0, unreadIndirectMessageCount: 0 }); | ||
322 | } | ||
323 | } | ||
324 | |||
299 | // Helpers | 325 | // Helpers |
300 | async _appStartsCounter() { | 326 | async _appStartsCounter() { |
301 | // we need to wait until the settings request is resolved | 327 | // we need to wait until the settings request is resolved |
@@ -326,6 +352,12 @@ export default class AppStore extends Store { | |||
326 | } | 352 | } |
327 | 353 | ||
328 | _systemDND() { | 354 | _systemDND() { |
329 | this.isSystemMuted = getDoNotDisturb(); | 355 | const dnd = getDoNotDisturb(); |
356 | if (dnd === this.stores.settings.all.isAppMuted || !this.isSystemMuteOverriden) { | ||
357 | this.actions.app.muteApp({ | ||
358 | isMuted: dnd, | ||
359 | overrideSystemMute: false, | ||
360 | }); | ||
361 | } | ||
330 | } | 362 | } |
331 | } | 363 | } |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 22c376c06..b04aafd78 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -488,19 +488,26 @@ export default class ServicesStore extends Store { | |||
488 | } | 488 | } |
489 | 489 | ||
490 | _getUnreadMessageCountReaction() { | 490 | _getUnreadMessageCountReaction() { |
491 | const showMessageBadgeWhenMuted = this.stores.settings.all.showMessageBadgeWhenMuted; | ||
492 | const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; | ||
493 | |||
491 | const unreadDirectMessageCount = this.enabled | 494 | const unreadDirectMessageCount = this.enabled |
495 | .filter(s => (showMessageBadgeWhenMuted || s.isNotificationEnabled) && showMessageBadgesEvenWhenMuted) | ||
492 | .map(s => s.unreadDirectMessageCount) | 496 | .map(s => s.unreadDirectMessageCount) |
493 | .reduce((a, b) => a + b, 0); | 497 | .reduce((a, b) => a + b, 0); |
494 | 498 | ||
495 | const unreadIndirectMessageCount = this.enabled | 499 | const unreadIndirectMessageCount = this.enabled |
496 | .filter(s => s.isIndirectMessageBadgeEnabled) | 500 | .filter(s => (showMessageBadgeWhenMuted || s.isIndirectMessageBadgeEnabled) && showMessageBadgesEvenWhenMuted) |
497 | .map(s => s.unreadIndirectMessageCount) | 501 | .map(s => s.unreadIndirectMessageCount) |
498 | .reduce((a, b) => a + b, 0); | 502 | .reduce((a, b) => a + b, 0); |
499 | 503 | ||
500 | this.actions.app.setBadge({ | 504 | // We can't just block this earlier, otherwise the mobx reaction won't be aware of the vars to watch in some cases |
501 | unreadDirectMessageCount, | 505 | if (showMessageBadgesEvenWhenMuted) { |
502 | unreadIndirectMessageCount, | 506 | this.actions.app.setBadge({ |
503 | }); | 507 | unreadDirectMessageCount, |
508 | unreadIndirectMessageCount, | ||
509 | }); | ||
510 | } | ||
504 | } | 511 | } |
505 | 512 | ||
506 | _logoutReaction() { | 513 | _logoutReaction() { |
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js index 30058f41d..33473f16d 100644 --- a/src/stores/SettingsStore.js +++ b/src/stores/SettingsStore.js | |||
@@ -5,6 +5,7 @@ import Store from './lib/Store'; | |||
5 | import Request from './lib/Request'; | 5 | import Request from './lib/Request'; |
6 | import CachedRequest from './lib/CachedRequest'; | 6 | import CachedRequest from './lib/CachedRequest'; |
7 | import { gaEvent } from '../lib/analytics'; | 7 | import { gaEvent } from '../lib/analytics'; |
8 | import SettingsModel from '../models/Settings'; | ||
8 | 9 | ||
9 | export default class SettingsStore extends Store { | 10 | export default class SettingsStore extends Store { |
10 | @observable allSettingsRequest = new CachedRequest(this.api.local, 'getSettings'); | 11 | @observable allSettingsRequest = new CachedRequest(this.api.local, 'getSettings'); |
@@ -25,7 +26,7 @@ export default class SettingsStore extends Store { | |||
25 | } | 26 | } |
26 | 27 | ||
27 | @computed get all() { | 28 | @computed get all() { |
28 | return this.allSettingsRequest.result || {}; | 29 | return this.allSettingsRequest.result || new SettingsModel(); |
29 | } | 30 | } |
30 | 31 | ||
31 | @action async _update({ settings }) { | 32 | @action async _update({ settings }) { |
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js index cb45b88b5..5e9cc9ba7 100644 --- a/src/stores/UIStore.js +++ b/src/stores/UIStore.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import { action, observable } from 'mobx'; | 1 | import { action, observable, computed } from 'mobx'; |
2 | 2 | ||
3 | import Store from './lib/Store'; | 3 | import Store from './lib/Store'; |
4 | 4 | ||
@@ -14,6 +14,12 @@ export default class UIStore extends Store { | |||
14 | this.actions.ui.toggleServiceUpdatedInfoBar.listen(this._toggleServiceUpdatedInfoBar.bind(this)); | 14 | this.actions.ui.toggleServiceUpdatedInfoBar.listen(this._toggleServiceUpdatedInfoBar.bind(this)); |
15 | } | 15 | } |
16 | 16 | ||
17 | @computed get showMessageBadgesEvenWhenMuted() { | ||
18 | const settings = this.stores.settings.all; | ||
19 | |||
20 | return (settings.isAppMuted && settings.showMessageBadgeWhenMuted) || !settings.isAppMuted; | ||
21 | } | ||
22 | |||
17 | // Actions | 23 | // Actions |
18 | @action _openSettings({ path = '/settings' }) { | 24 | @action _openSettings({ path = '/settings' }) { |
19 | const settingsPath = path !== '/settings' ? `/settings/${path}` : path; | 25 | const settingsPath = path !== '/settings' ? `/settings/${path}` : path; |
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 1cb2ecac3..09000dcdb 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js | |||
@@ -26,6 +26,7 @@ export default class UserStore extends Store { | |||
26 | @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); | 26 | @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); |
27 | @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); | 27 | @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); |
28 | @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices'); | 28 | @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices'); |
29 | @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete'); | ||
29 | 30 | ||
30 | @observable isImportLegacyServicesExecuting = false; | 31 | @observable isImportLegacyServicesExecuting = false; |
31 | @observable isImportLegacyServicesCompleted = false; | 32 | @observable isImportLegacyServicesCompleted = false; |
@@ -57,6 +58,7 @@ export default class UserStore extends Store { | |||
57 | this.actions.user.update.listen(this._update.bind(this)); | 58 | this.actions.user.update.listen(this._update.bind(this)); |
58 | this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); | 59 | this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); |
59 | this.actions.user.importLegacyServices.listen(this._importLegacyServices.bind(this)); | 60 | this.actions.user.importLegacyServices.listen(this._importLegacyServices.bind(this)); |
61 | this.actions.user.delete.listen(this._delete.bind(this)); | ||
60 | 62 | ||
61 | // Reactions | 63 | // Reactions |
62 | this.registerReactions([ | 64 | this.registerReactions([ |
@@ -212,6 +214,10 @@ export default class UserStore extends Store { | |||
212 | this.isImportLegacyServicesCompleted = true; | 214 | this.isImportLegacyServicesCompleted = true; |
213 | } | 215 | } |
214 | 216 | ||
217 | @action async _delete() { | ||
218 | this.deleteAccountRequest.execute(); | ||
219 | } | ||
220 | |||
215 | // This is a mobx autorun which forces the user to login if not authenticated | 221 | // This is a mobx autorun which forces the user to login if not authenticated |
216 | _requireAuthenticatedUser = () => { | 222 | _requireAuthenticatedUser = () => { |
217 | if (this.isTokenExpired) { | 223 | if (this.isTokenExpired) { |
diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 6e93094b4..73cef0813 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss | |||
@@ -281,6 +281,10 @@ | |||
281 | margin-left: auto; | 281 | margin-left: auto; |
282 | } | 282 | } |
283 | 283 | ||
284 | .franz-form__button { | ||
285 | white-space: nowrap; | ||
286 | } | ||
287 | |||
284 | div { | 288 | div { |
285 | height: auto; | 289 | height: auto; |
286 | } | 290 | } |
diff --git a/src/styles/tabs.scss b/src/styles/tabs.scss index 3ffc53558..ac48aabd6 100644 --- a/src/styles/tabs.scss +++ b/src/styles/tabs.scss | |||
@@ -78,6 +78,26 @@ | |||
78 | } | 78 | } |
79 | } | 79 | } |
80 | 80 | ||
81 | .tab-item__info-badge { | ||
82 | width: 17px; | ||
83 | height: 17px; | ||
84 | background: $theme-gray-light; | ||
85 | color: $theme-gray-lighter; | ||
86 | border-radius: 20px; | ||
87 | padding: 0px 5px; | ||
88 | font-size: 11px; | ||
89 | position: absolute; | ||
90 | right: 8px; | ||
91 | bottom: 8px; | ||
92 | display: flex; | ||
93 | justify-content: center; | ||
94 | align-items: center; | ||
95 | |||
96 | &.is-indirect { | ||
97 | padding-top: 0px; | ||
98 | } | ||
99 | } | ||
100 | |||
81 | &.is-reordering { | 101 | &.is-reordering { |
82 | z-index: 99999; | 102 | z-index: 99999; |
83 | } | 103 | } |
diff --git a/src/styles/welcome.scss b/src/styles/welcome.scss index 5365921fb..cfdcc80ad 100644 --- a/src/styles/welcome.scss +++ b/src/styles/welcome.scss | |||
@@ -58,17 +58,32 @@ | |||
58 | } | 58 | } |
59 | 59 | ||
60 | &__featured-services { | 60 | &__featured-services { |
61 | margin-top: 150px; | ||
62 | text-align: center; | 61 | text-align: center; |
63 | margin-top: 80px; | 62 | width: 480px; |
63 | margin: 80px auto 0 auto; | ||
64 | display: flex; | ||
65 | align-items: center; | ||
66 | flex-wrap: wrap; | ||
67 | background: #FFF; | ||
68 | border-radius: 6px; | ||
69 | padding: 20px 20px 5px; | ||
64 | } | 70 | } |
65 | 71 | ||
66 | &__featured-service { | 72 | &__featured-service { |
67 | width: 35px; | 73 | width: 35px; |
68 | margin-right: 30px; | 74 | height: 35px; |
75 | margin: 0 10px 15px; | ||
76 | filter: grayscale(1) | ||
77 | opacity(0.5); | ||
78 | transition: 0.5s filter, 0.5s opacity; | ||
79 | |||
80 | &:hover { | ||
81 | filter: grayscale(0); | ||
82 | opacity: (1); | ||
83 | } | ||
69 | 84 | ||
70 | &:last-of-type { | 85 | img { |
71 | margin-right: 0; | 86 | width: 35px; |
72 | } | 87 | } |
73 | } | 88 | } |
74 | } | 89 | } |