aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--README.md6
-rw-r--r--appveyor.yml2
-rw-r--r--build-helpers/images/icon.pngbin547480 -> 126364 bytes
-rw-r--r--build-helpers/images/icons/1024x1024.pngbin547480 -> 126364 bytes
-rw-r--r--build-helpers/images/icons/128x128.pngbin0 -> 5538 bytes
-rw-r--r--build-helpers/images/icons/16x16.pngbin0 -> 682 bytes
-rw-r--r--build-helpers/images/icons/24x24.pngbin0 -> 1143 bytes
-rw-r--r--build-helpers/images/icons/256x256.pngbin0 -> 13267 bytes
-rw-r--r--build-helpers/images/icons/32x32.pngbin0 -> 1526 bytes
-rw-r--r--build-helpers/images/icons/48x48.pngbin0 -> 2262 bytes
-rw-r--r--build-helpers/images/icons/512x512.pngbin0 -> 38356 bytes
-rw-r--r--build-helpers/images/icons/64x64.pngbin0 -> 2885 bytes
-rw-r--r--build-helpers/images/icons/96x96.pngbin0 -> 4422 bytes
-rw-r--r--electron-builder.yml7
-rw-r--r--package.json2
-rw-r--r--src/actions/app.js1
-rw-r--r--src/actions/user.js1
-rw-r--r--src/api/UserApi.js4
-rw-r--r--src/api/server/ServerApi.js28
-rw-r--r--src/components/auth/Import.js2
-rw-r--r--src/components/auth/Welcome.js12
-rw-r--r--src/components/layout/Sidebar.js8
-rw-r--r--src/components/services/tabs/TabBarSortableList.js6
-rw-r--r--src/components/services/tabs/TabItem.js37
-rw-r--r--src/components/services/tabs/Tabbar.js6
-rw-r--r--src/components/settings/account/AccountDashboard.js49
-rw-r--r--src/components/settings/settings/EditSettingsForm.js1
-rw-r--r--src/components/ui/Button.js2
-rw-r--r--src/config.js1
-rw-r--r--src/containers/layout/AppLayoutContainer.js8
-rw-r--r--src/containers/settings/AccountScreen.js5
-rw-r--r--src/containers/settings/EditSettingsScreen.js10
-rw-r--r--src/electron/deepLinking.js5
-rw-r--r--src/helpers/async-helpers.js5
-rw-r--r--src/i18n/locales/el.json2
-rw-r--r--src/i18n/locales/en-US.json13
-rw-r--r--src/i18n/locales/es.json2
-rw-r--r--src/index.js30
-rw-r--r--src/lib/Menu.js2
-rw-r--r--src/models/Settings.js1
-rw-r--r--src/stores/AppStore.js40
-rw-r--r--src/stores/ServicesStore.js17
-rw-r--r--src/stores/SettingsStore.js3
-rw-r--r--src/stores/UIStore.js8
-rw-r--r--src/stores/UserStore.js6
-rw-r--r--src/styles/settings.scss4
-rw-r--r--src/styles/tabs.scss20
-rw-r--r--src/styles/welcome.scss25
-rw-r--r--yarn.lock25
50 files changed, 341 insertions, 69 deletions
diff --git a/.travis.yml b/.travis.yml
index b3ebc4f25..78c1e3693 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,8 +9,6 @@ matrix:
9language: node_js 9language: node_js
10 10
11before_script: 11before_script:
12- yarn add global gulp-cli@1.2.2
13- yarn add global gulpjs/gulp#4.0
14- yarn add global node-sass 12- yarn add global node-sass
15- yarn install 13- yarn install
16 14
@@ -19,7 +17,7 @@ script:
19- travis_wait yarn build 17- travis_wait yarn build
20 18
21node_js: 19node_js:
22- '7' 20- '8'
23 21
24before_install: 22before_install:
25- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt install libx11-dev libxext-dev libxss-dev libxkbfile-dev; fi 23- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt install libx11-dev libxext-dev libxss-dev libxkbfile-dev; fi
diff --git a/README.md b/README.md
index 6110573a8..099100284 100644
--- a/README.md
+++ b/README.md
@@ -31,12 +31,6 @@ $ brew install yarn
31##### Linux 31##### Linux
32[Install Yarn on Linux](https://yarnpkg.com/lang/en/docs/install/) 32[Install Yarn on Linux](https://yarnpkg.com/lang/en/docs/install/)
33 33
34#### Install Gulp 4
35```bash
36$ yarn add global gulp-cli@1.2.2
37$ yarn add global gulpjs/gulp#4.0
38```
39
40#### Fix native modules to match current electron node version 34#### Fix native modules to match current electron node version
41```bash 35```bash
42$ yarn run rebuild 36$ yarn run rebuild
diff --git a/appveyor.yml b/appveyor.yml
index a8e55d25f..d9296b1f6 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -11,8 +11,6 @@ version: 5.0.0.{build}
11install: 11install:
12 - ps: Install-Product node 8 12 - ps: Install-Product node 8
13 - yarn cache clean 13 - yarn cache clean
14 - yarn add global gulp-cli@1.2.2
15 - yarn add global gulpjs/gulp#4.0
16 - yarn install 14 - yarn install
17 15
18# cache: 16# cache:
diff --git a/build-helpers/images/icon.png b/build-helpers/images/icon.png
index e0b4935b5..9c39f06e0 100644
--- a/build-helpers/images/icon.png
+++ b/build-helpers/images/icon.png
Binary files differ
diff --git a/build-helpers/images/icons/1024x1024.png b/build-helpers/images/icons/1024x1024.png
index e0b4935b5..9c39f06e0 100644
--- a/build-helpers/images/icons/1024x1024.png
+++ b/build-helpers/images/icons/1024x1024.png
Binary files differ
diff --git a/build-helpers/images/icons/128x128.png b/build-helpers/images/icons/128x128.png
new file mode 100644
index 000000000..8eb811283
--- /dev/null
+++ b/build-helpers/images/icons/128x128.png
Binary files differ
diff --git a/build-helpers/images/icons/16x16.png b/build-helpers/images/icons/16x16.png
new file mode 100644
index 000000000..2523dd8a7
--- /dev/null
+++ b/build-helpers/images/icons/16x16.png
Binary files differ
diff --git a/build-helpers/images/icons/24x24.png b/build-helpers/images/icons/24x24.png
new file mode 100644
index 000000000..9cb17a0b1
--- /dev/null
+++ b/build-helpers/images/icons/24x24.png
Binary files differ
diff --git a/build-helpers/images/icons/256x256.png b/build-helpers/images/icons/256x256.png
new file mode 100644
index 000000000..5742147b8
--- /dev/null
+++ b/build-helpers/images/icons/256x256.png
Binary files differ
diff --git a/build-helpers/images/icons/32x32.png b/build-helpers/images/icons/32x32.png
new file mode 100644
index 000000000..09b88a805
--- /dev/null
+++ b/build-helpers/images/icons/32x32.png
Binary files differ
diff --git a/build-helpers/images/icons/48x48.png b/build-helpers/images/icons/48x48.png
new file mode 100644
index 000000000..c9d2f331c
--- /dev/null
+++ b/build-helpers/images/icons/48x48.png
Binary files differ
diff --git a/build-helpers/images/icons/512x512.png b/build-helpers/images/icons/512x512.png
new file mode 100644
index 000000000..9d5f1c658
--- /dev/null
+++ b/build-helpers/images/icons/512x512.png
Binary files differ
diff --git a/build-helpers/images/icons/64x64.png b/build-helpers/images/icons/64x64.png
new file mode 100644
index 000000000..7842217ed
--- /dev/null
+++ b/build-helpers/images/icons/64x64.png
Binary files differ
diff --git a/build-helpers/images/icons/96x96.png b/build-helpers/images/icons/96x96.png
new file mode 100644
index 000000000..bbae07f45
--- /dev/null
+++ b/build-helpers/images/icons/96x96.png
Binary files differ
diff --git a/electron-builder.yml b/electron-builder.yml
index adc8f8ae9..96bd63cc2 100644
--- a/electron-builder.yml
+++ b/electron-builder.yml
@@ -24,10 +24,17 @@ linux:
24 icon: ./build-helpers/images/icons 24 icon: ./build-helpers/images/icons
25 category: Network;InstantMessaging; 25 category: Network;InstantMessaging;
26 executableName: franz 26 executableName: franz
27 synopsis: "Messaging app for WhatsApp, Slack, Telegram, HipChat, Hangouts and many many more."
28 description: "Franz is your messaging app / former Emperor of Austria and combines chat & messaging services into one application. Franz currently supports Slack, WhatsApp, WeChat, HipChat, Facebook Messenger, Telegram, Google Hangouts, GroupMe, Skype and many more. You can download Franz for free for Mac & Windows."
27 target: 29 target:
28 - target: AppImage 30 - target: AppImage
29 - target: deb 31 - target: deb
32 - target: tar.gz
30 33
31nsis: 34nsis:
32 perMachine: false 35 perMachine: false
33 oneClick: true 36 oneClick: true
37
38protocols:
39 name: Franz
40 schemes: [franz]
diff --git a/package.json b/package.json
index 54df530ca..9c111d336 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
6 "description": "Messaging app for WhatsApp, Slack, Telegram, HipChat, Hangouts and many many more.", 6 "description": "Messaging app for WhatsApp, Slack, Telegram, HipChat, Hangouts and many many more.",
7 "copyright": "adlk x franz - Stefan Malzner", 7 "copyright": "adlk x franz - Stefan Malzner",
8 "main": "index.js", 8 "main": "index.js",
9 "homepage": "https://meetfranz.com",
9 "repository": "https://github.com/meetfranz/franz.git", 10 "repository": "https://github.com/meetfranz/franz.git",
10 "private": true, 11 "private": true,
11 "scripts": { 12 "scripts": {
@@ -38,6 +39,7 @@
38 "electron-updater": "^2.4.3", 39 "electron-updater": "^2.4.3",
39 "electron-window-state": "^4.1.0", 40 "electron-window-state": "^4.1.0",
40 "fs-extra": "^3.0.1", 41 "fs-extra": "^3.0.1",
42 "gulp-cli": "1.2.2",
41 "ini": "^1.3.4", 43 "ini": "^1.3.4",
42 "jshashes": "^1.0.6", 44 "jshashes": "^1.0.6",
43 "jsonwebtoken": "^7.4.1", 45 "jsonwebtoken": "^7.4.1",
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';
12import UserModel from '../../models/User'; 12import UserModel from '../../models/User';
13import OrderModel from '../../models/Order'; 13import OrderModel from '../../models/Order';
14 14
15import { sleep } from '../../helpers/async-helpers';
16
15import { API } from '../../environment'; 17import { API } from '../../environment';
16 18
17import { 19import {
@@ -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 @@
1export 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
3export 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';
8import ipcApi from './electron/ipc-api'; 8import ipcApi from './electron/ipc-api';
9import Tray from './lib/Tray'; 9import Tray from './lib/Tray';
10import Settings from './electron/Settings'; 10import Settings from './electron/Settings';
11import handleDeepLink from './electron/deepLinking';
11import { appId } from './package.json'; // eslint-disable-line import/no-unresolved 12import { appId } from './package.json'; // eslint-disable-line import/no-unresolved
12import './electron/exception'; 13import './electron/exception';
13 14
@@ -26,10 +27,20 @@ if (isWindows) {
26} 27}
27 28
28// Force single window 29// Force single window
29const isSecondInstance = app.makeSingleInstance(() => { 30const 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
194app.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
204app.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';
5import Request from './lib/Request'; 5import Request from './lib/Request';
6import CachedRequest from './lib/CachedRequest'; 6import CachedRequest from './lib/CachedRequest';
7import { gaEvent } from '../lib/analytics'; 7import { gaEvent } from '../lib/analytics';
8import SettingsModel from '../models/Settings';
8 9
9export default class SettingsStore extends Store { 10export 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 @@
1import { action, observable } from 'mobx'; 1import { action, observable, computed } from 'mobx';
2 2
3import Store from './lib/Store'; 3import 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 }
diff --git a/yarn.lock b/yarn.lock
index 6c3f807a4..c662ce63f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2974,6 +2974,29 @@ gulp-babel@^6.1.2:
2974 through2 "^2.0.0" 2974 through2 "^2.0.0"
2975 vinyl-sourcemaps-apply "^0.2.0" 2975 vinyl-sourcemaps-apply "^0.2.0"
2976 2976
2977gulp-cli@1.2.2:
2978 version "1.2.2"
2979 resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-1.2.2.tgz#7392def6316c6e7939a4f296f3f540151ae3a275"
2980 dependencies:
2981 archy "^1.0.0"
2982 chalk "^1.1.0"
2983 fancy-log "^1.1.0"
2984 gulplog "^1.0.0"
2985 interpret "^1.0.0"
2986 liftoff "^2.1.0"
2987 lodash.isfunction "^3.0.8"
2988 lodash.isplainobject "^4.0.4"
2989 lodash.isstring "^4.0.1"
2990 lodash.sortby "^4.5.0"
2991 matchdep "^1.0.0"
2992 mute-stdout "^1.0.0"
2993 pretty-hrtime "^1.0.0"
2994 semver-greatest-satisfied-range "^1.0.0"
2995 tildify "^1.0.0"
2996 v8flags "^2.0.9"
2997 wreck "^6.3.0"
2998 yargs "^3.28.0"
2999
2977gulp-cli@^1.0.0: 3000gulp-cli@^1.0.0:
2978 version "1.4.0" 3001 version "1.4.0"
2979 resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-1.4.0.tgz#6f5bbe2cd0bdb4849d12cf9e1246a5861f8b4f88" 3002 resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-1.4.0.tgz#6f5bbe2cd0bdb4849d12cf9e1246a5861f8b4f88"
@@ -3768,7 +3791,7 @@ levn@^0.3.0, levn@~0.3.0:
3768 prelude-ls "~1.1.2" 3791 prelude-ls "~1.1.2"
3769 type-check "~0.3.2" 3792 type-check "~0.3.2"
3770 3793
3771liftoff@^2.3.0: 3794liftoff@^2.1.0, liftoff@^2.3.0:
3772 version "2.3.0" 3795 version "2.3.0"
3773 resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.3.0.tgz#a98f2ff67183d8ba7cfaca10548bd7ff0550b385" 3796 resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.3.0.tgz#a98f2ff67183d8ba7cfaca10548bd7ff0550b385"
3774 dependencies: 3797 dependencies: