aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/layout/AppLayout.js8
-rw-r--r--src/components/layout/Sidebar.js54
-rw-r--r--src/components/services/content/ServiceDisabled.js48
-rw-r--r--src/components/services/content/ServiceWebview.js59
-rw-r--r--src/components/services/content/Services.js15
-rw-r--r--src/components/services/content/WebviewCrashHandler.js81
-rw-r--r--src/components/services/tabs/TabBarSortableList.js25
-rw-r--r--src/components/services/tabs/TabItem.js26
-rw-r--r--src/components/services/tabs/Tabbar.js21
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js1
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js57
-rw-r--r--src/components/settings/services/EditServiceForm.js14
-rw-r--r--src/components/settings/services/ServiceItem.js22
-rw-r--r--src/components/settings/settings/EditSettingsForm.js46
-rw-r--r--src/components/ui/AppLoader.js2
-rw-r--r--src/components/ui/InfoBar.js7
-rw-r--r--src/components/ui/Loader.js5
-rw-r--r--src/components/ui/Subscription.js19
18 files changed, 404 insertions, 106 deletions
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index f60c170a8..20dc2f764 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -23,6 +23,10 @@ const messages = defineMessages({
23 id: 'infobar.buttonReloadServices', 23 id: 'infobar.buttonReloadServices',
24 defaultMessage: '!!!Reload services', 24 defaultMessage: '!!!Reload services',
25 }, 25 },
26 changelog: {
27 id: 'infobar.buttonChangelog',
28 defaultMessage: '!!!Changelog',
29 },
26 buttonInstallUpdate: { 30 buttonInstallUpdate: {
27 id: 'infobar.buttonInstallUpdate', 31 id: 'infobar.buttonInstallUpdate',
28 defaultMessage: '!!!Restart & install update', 32 defaultMessage: '!!!Restart & install update',
@@ -135,7 +139,9 @@ export default class AppLayout extends Component {
135 sticky 139 sticky
136 > 140 >
137 <span className="mdi mdi-information" /> 141 <span className="mdi mdi-information" />
138 {intl.formatMessage(messages.updateAvailable)} 142 {intl.formatMessage(messages.updateAvailable)} <a href="https://meetfranz.com/changelog" target="_blank">
143 <u>{intl.formatMessage(messages.changelog)}</u>
144 </a>
139 </InfoBar> 145 </InfoBar>
140 )} 146 )}
141 {services} 147 {services}
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js
index 6a5c0f365..cb2ecc8ce 100644
--- a/src/components/layout/Sidebar.js
+++ b/src/components/layout/Sidebar.js
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import ReactTooltip from 'react-tooltip'; 3import ReactTooltip from 'react-tooltip';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import { observer } from 'mobx-react';
5 6
6import Tabbar from '../services/tabs/Tabbar'; 7import Tabbar from '../services/tabs/Tabbar';
7import { ctrlKey } from '../../environment'; 8import { ctrlKey } from '../../environment';
@@ -11,16 +12,26 @@ const messages = defineMessages({
11 id: 'sidebar.settings', 12 id: 'sidebar.settings',
12 defaultMessage: '!!!Settings', 13 defaultMessage: '!!!Settings',
13 }, 14 },
15 addNewService: {
16 id: 'sidebar.addNewService',
17 defaultMessage: '!!!Add new service',
18 },
19 mute: {
20 id: 'sidebar.mute',
21 defaultMessage: '!!!Disable audio',
22 },
23 unmute: {
24 id: 'sidebar.unmute',
25 defaultMessage: '!!!Enable audio',
26 },
14}); 27});
15 28
29@observer
16export default class Sidebar extends Component { 30export default class Sidebar extends Component {
17 static propTypes = { 31 static propTypes = {
18 openSettings: PropTypes.func.isRequired, 32 openSettings: PropTypes.func.isRequired,
19 isPremiumUser: PropTypes.bool, 33 toggleMuteApp: PropTypes.func.isRequired,
20 } 34 isAppMuted: PropTypes.bool.isRequired,
21
22 static defaultProps = {
23 isPremiumUser: false,
24 } 35 }
25 36
26 static contextTypes = { 37 static contextTypes = {
@@ -40,8 +51,9 @@ export default class Sidebar extends Component {
40 } 51 }
41 52
42 render() { 53 render() {
43 const { openSettings, isPremiumUser } = this.props; 54 const { openSettings, toggleMuteApp, isAppMuted } = this.props;
44 const { intl } = this.context; 55 const { intl } = this.context;
56
45 return ( 57 return (
46 <div className="sidebar"> 58 <div className="sidebar">
47 <Tabbar 59 <Tabbar
@@ -50,21 +62,25 @@ export default class Sidebar extends Component {
50 disableToolTip={() => this.disableToolTip()} 62 disableToolTip={() => this.disableToolTip()}
51 /> 63 />
52 <button 64 <button
53 onClick={openSettings} 65 onClick={toggleMuteApp}
54 className="sidebar__settings-button" 66 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`}
67 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`}
68 >
69 <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} />
70 </button>
71 <button
72 onClick={() => openSettings({ path: 'recipes' })}
73 className="sidebar__button sidebar__button--new-service"
74 data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`}
75 >
76 <i className="mdi mdi-plus-box" />
77 </button>
78 <button
79 onClick={() => openSettings({ path: 'app' })}
80 className="sidebar__button sidebar__button--settings"
55 data-tip={`${intl.formatMessage(messages.settings)} (${ctrlKey}+,)`} 81 data-tip={`${intl.formatMessage(messages.settings)} (${ctrlKey}+,)`}
56 > 82 >
57 {isPremiumUser && ( 83 <i className="mdi mdi-settings" />
58 <span className="emoji">
59 <img src="./assets/images/emoji/star.png" alt="" />
60 </span>
61 )}
62 <img
63 src="./assets/images/logo.svg"
64 className="sidebar__logo"
65 alt=""
66 />
67 {intl.formatMessage(messages.settings)}
68 </button> 84 </button>
69 {this.state.tooltipEnabled && ( 85 {this.state.tooltipEnabled && (
70 <ReactTooltip place="right" type="dark" effect="solid" /> 86 <ReactTooltip place="right" type="dark" effect="solid" />
diff --git a/src/components/services/content/ServiceDisabled.js b/src/components/services/content/ServiceDisabled.js
new file mode 100644
index 000000000..b5af3743d
--- /dev/null
+++ b/src/components/services/content/ServiceDisabled.js
@@ -0,0 +1,48 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Button from '../../ui/Button';
7
8const messages = defineMessages({
9 headline: {
10 id: 'service.disabledHandler.headline',
11 defaultMessage: '!!!{name} is disabled',
12 },
13 action: {
14 id: 'service.disabledHandler.action',
15 defaultMessage: '!!!Enable {name}',
16 },
17});
18
19@observer
20export default class ServiceDisabled extends Component {
21 static propTypes = {
22 name: PropTypes.string.isRequired,
23 enable: PropTypes.func.isRequired,
24 };
25
26 static contextTypes = {
27 intl: intlShape,
28 };
29
30 countdownInterval = null;
31 countdownIntervalTimeout = 1000;
32
33 render() {
34 const { name, enable } = this.props;
35 const { intl } = this.context;
36
37 return (
38 <div className="services__info-layer">
39 <h1>{intl.formatMessage(messages.headline, { name })}</h1>
40 <Button
41 label={intl.formatMessage(messages.action, { name })}
42 buttonType="inverted"
43 onClick={() => enable()}
44 />
45 </div>
46 );
47 }
48}
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index 3ee3155be..faa356d3d 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -7,12 +7,17 @@ import classnames from 'classnames';
7 7
8import ServiceModel from '../../../models/Service'; 8import ServiceModel from '../../../models/Service';
9import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; 9import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl';
10import WebviewCrashHandler from './WebviewCrashHandler';
11import ServiceDisabled from './ServiceDisabled';
10 12
11@observer 13@observer
12export default class ServiceWebview extends Component { 14export default class ServiceWebview extends Component {
13 static propTypes = { 15 static propTypes = {
14 service: PropTypes.instanceOf(ServiceModel).isRequired, 16 service: PropTypes.instanceOf(ServiceModel).isRequired,
15 setWebviewReference: PropTypes.func.isRequired, 17 setWebviewReference: PropTypes.func.isRequired,
18 reload: PropTypes.func.isRequired,
19 isAppMuted: PropTypes.bool.isRequired,
20 enable: PropTypes.func.isRequired,
16 }; 21 };
17 22
18 static defaultProps = { 23 static defaultProps = {
@@ -53,6 +58,9 @@ export default class ServiceWebview extends Component {
53 const { 58 const {
54 service, 59 service,
55 setWebviewReference, 60 setWebviewReference,
61 reload,
62 isAppMuted,
63 enable,
56 } = this.props; 64 } = this.props;
57 65
58 const webviewClasses = classnames({ 66 const webviewClasses = classnames({
@@ -70,26 +78,37 @@ export default class ServiceWebview extends Component {
70 78
71 return ( 79 return (
72 <div className={webviewClasses}> 80 <div className={webviewClasses}>
73 <Webview 81 {service.hasCrashed && (
74 ref={(element) => { this.webview = element; }} 82 <WebviewCrashHandler
75 83 name={service.recipe.name}
76 autosize 84 webview={service.webview}
77 src={service.url} 85 reload={reload}
78 preload="./webview/plugin.js" 86 />
79 partition={`persist:service-${service.id}`} 87 )}
80 88 {!service.isEnabled ? (
81 onDidAttach={() => setWebviewReference({ 89 <ServiceDisabled
82 serviceId: service.id, 90 name={service.recipe.name}
83 webview: this.webview.view, 91 webview={service.webview}
84 })} 92 enable={enable}
85 93 />
86 onUpdateTargetUrl={this.updateTargetUrl} 94 ) : (
87 95 <Webview
88 useragent={service.userAgent} 96 ref={(element) => { this.webview = element; }}
89 97 autosize
90 disablewebsecurity 98 src={service.url}
91 allowpopups 99 preload="./webview/plugin.js"
92 /> 100 partition={`persist:service-${service.id}`}
101 onDidAttach={() => setWebviewReference({
102 serviceId: service.id,
103 webview: this.webview.view,
104 })}
105 onUpdateTargetUrl={this.updateTargetUrl}
106 useragent={service.userAgent}
107 muted={isAppMuted || service.isMuted}
108 disablewebsecurity
109 allowpopups
110 />
111 )}
93 {statusBar} 112 {statusBar}
94 </div> 113 </div>
95 ); 114 );
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index 03c68b06f..b1322afc2 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -25,6 +25,9 @@ export default class Services extends Component {
25 setWebviewReference: PropTypes.func.isRequired, 25 setWebviewReference: PropTypes.func.isRequired,
26 handleIPCMessage: PropTypes.func.isRequired, 26 handleIPCMessage: PropTypes.func.isRequired,
27 openWindow: PropTypes.func.isRequired, 27 openWindow: PropTypes.func.isRequired,
28 reload: PropTypes.func.isRequired,
29 isAppMuted: PropTypes.bool.isRequired,
30 update: PropTypes.func.isRequired,
28 }; 31 };
29 32
30 static defaultProps = { 33 static defaultProps = {
@@ -42,6 +45,9 @@ export default class Services extends Component {
42 handleIPCMessage, 45 handleIPCMessage,
43 setWebviewReference, 46 setWebviewReference,
44 openWindow, 47 openWindow,
48 reload,
49 isAppMuted,
50 update,
45 } = this.props; 51 } = this.props;
46 const { intl } = this.context; 52 const { intl } = this.context;
47 53
@@ -73,6 +79,15 @@ export default class Services extends Component {
73 handleIPCMessage={handleIPCMessage} 79 handleIPCMessage={handleIPCMessage}
74 setWebviewReference={setWebviewReference} 80 setWebviewReference={setWebviewReference}
75 openWindow={openWindow} 81 openWindow={openWindow}
82 reload={() => reload({ serviceId: service.id })}
83 isAppMuted={isAppMuted}
84 enable={() => update({
85 serviceId: service.id,
86 serviceData: {
87 isEnabled: true,
88 },
89 redirect: false,
90 })}
76 /> 91 />
77 ))} 92 ))}
78 </div> 93 </div>
diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js
new file mode 100644
index 000000000..d3e6951f3
--- /dev/null
+++ b/src/components/services/content/WebviewCrashHandler.js
@@ -0,0 +1,81 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Button from '../../ui/Button';
7
8const messages = defineMessages({
9 headline: {
10 id: 'service.crashHandler.headline',
11 defaultMessage: '!!!Oh no!',
12 },
13 text: {
14 id: 'service.crashHandler.text',
15 defaultMessage: '!!!{name} has caused an error.',
16 },
17 action: {
18 id: 'service.crashHandler.action',
19 defaultMessage: '!!!Reload {name}',
20 },
21 autoReload: {
22 id: 'service.crashHandler.autoReload',
23 defaultMessage: '!!!Trying to automatically restore {name} in {seconds} seconds',
24 },
25});
26
27@observer
28export default class WebviewCrashHandler extends Component {
29 static propTypes = {
30 name: PropTypes.string.isRequired,
31 reload: PropTypes.func.isRequired,
32 };
33
34 static contextTypes = {
35 intl: intlShape,
36 };
37
38 state = {
39 countdown: 10000,
40 }
41
42 componentDidMount() {
43 const { reload } = this.props;
44
45 this.countdownInterval = setInterval(() => {
46 this.setState({
47 countdown: this.state.countdown - this.countdownIntervalTimeout,
48 });
49
50 if (this.state.countdown <= 0) {
51 reload();
52 clearInterval(this.countdownInterval);
53 }
54 }, this.countdownIntervalTimeout);
55 }
56
57 countdownInterval = null;
58 countdownIntervalTimeout = 1000;
59
60 render() {
61 const { name, reload } = this.props;
62 const { intl } = this.context;
63
64 return (
65 <div className="services__info-layer">
66 <h1>{intl.formatMessage(messages.headline)}</h1>
67 <p>{intl.formatMessage(messages.text, { name })}</p>
68 <Button
69 // label={`Reload ${name}`}
70 label={intl.formatMessage(messages.action, { name })}
71 buttonType="inverted"
72 onClick={() => reload()}
73 />
74 <p className="footnote">{intl.formatMessage(messages.autoReload, {
75 name,
76 seconds: this.state.countdown / 1000,
77 })}</p>
78 </div>
79 );
80 }
81}
diff --git a/src/components/services/tabs/TabBarSortableList.js b/src/components/services/tabs/TabBarSortableList.js
index e5ae36419..2daf55676 100644
--- a/src/components/services/tabs/TabBarSortableList.js
+++ b/src/components/services/tabs/TabBarSortableList.js
@@ -2,17 +2,8 @@ import React, { Component } from 'react';
2import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 2import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import { SortableContainer } from 'react-sortable-hoc'; 4import { SortableContainer } from 'react-sortable-hoc';
5import { defineMessages, intlShape } from 'react-intl';
6 5
7import TabItem from './TabItem'; 6import TabItem from './TabItem';
8import { ctrlKey } from '../../../environment';
9
10const messages = defineMessages({
11 addNewService: {
12 id: 'sidebar.addNewService',
13 defaultMessage: '!!!Add new service',
14 },
15});
16 7
17@observer 8@observer
18class TabBarSortableList extends Component { 9class TabBarSortableList extends Component {
@@ -22,27 +13,25 @@ class TabBarSortableList extends Component {
22 openSettings: PropTypes.func.isRequired, 13 openSettings: PropTypes.func.isRequired,
23 reload: PropTypes.func.isRequired, 14 reload: PropTypes.func.isRequired,
24 toggleNotifications: PropTypes.func.isRequired, 15 toggleNotifications: PropTypes.func.isRequired,
16 toggleAudio: PropTypes.func.isRequired,
25 deleteService: PropTypes.func.isRequired, 17 deleteService: PropTypes.func.isRequired,
26 disableService: PropTypes.func.isRequired, 18 disableService: PropTypes.func.isRequired,
19 enableService: PropTypes.func.isRequired,
27 } 20 }
28 21
29 static contextTypes = {
30 intl: intlShape,
31 };
32
33 render() { 22 render() {
34 const { 23 const {
35 services, 24 services,
36 setActive, 25 setActive,
37 reload, 26 reload,
38 toggleNotifications, 27 toggleNotifications,
28 toggleAudio,
39 deleteService, 29 deleteService,
40 disableService, 30 disableService,
31 enableService,
41 openSettings, 32 openSettings,
42 } = this.props; 33 } = this.props;
43 34
44 const { intl } = this.context;
45
46 return ( 35 return (
47 <ul 36 <ul
48 className="tabs" 37 className="tabs"
@@ -56,12 +45,14 @@ class TabBarSortableList extends Component {
56 shortcutIndex={index + 1} 45 shortcutIndex={index + 1}
57 reload={() => reload({ serviceId: service.id })} 46 reload={() => reload({ serviceId: service.id })}
58 toggleNotifications={() => toggleNotifications({ serviceId: service.id })} 47 toggleNotifications={() => toggleNotifications({ serviceId: service.id })}
48 toggleAudio={() => toggleAudio({ serviceId: service.id })}
59 deleteService={() => deleteService({ serviceId: service.id })} 49 deleteService={() => deleteService({ serviceId: service.id })}
60 disableService={() => disableService({ serviceId: service.id })} 50 disableService={() => disableService({ serviceId: service.id })}
51 enableService={() => enableService({ serviceId: service.id })}
61 openSettings={openSettings} 52 openSettings={openSettings}
62 /> 53 />
63 ))} 54 ))}
64 <li> 55 {/* <li>
65 <button 56 <button
66 className="sidebar__add-service" 57 className="sidebar__add-service"
67 onClick={() => openSettings({ path: 'recipes' })} 58 onClick={() => openSettings({ path: 'recipes' })}
@@ -69,7 +60,7 @@ class TabBarSortableList extends Component {
69 > 60 >
70 <span className="mdi mdi-plus" /> 61 <span className="mdi mdi-plus" />
71 </button> 62 </button>
72 </li> 63 </li> */}
73 </ul> 64 </ul>
74 ); 65 );
75 } 66 }
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
index 9e03d2e21..a7136c43f 100644
--- a/src/components/services/tabs/TabItem.js
+++ b/src/components/services/tabs/TabItem.js
@@ -28,10 +28,22 @@ const messages = defineMessages({
28 id: 'tabs.item.enableNotification', 28 id: 'tabs.item.enableNotification',
29 defaultMessage: '!!!Enable notifications', 29 defaultMessage: '!!!Enable notifications',
30 }, 30 },
31 disableAudio: {
32 id: 'tabs.item.disableAudio',
33 defaultMessage: '!!!Disable audio',
34 },
35 enableAudio: {
36 id: 'tabs.item.enableAudio',
37 defaultMessage: '!!!Enable audio',
38 },
31 disableService: { 39 disableService: {
32 id: 'tabs.item.disableService', 40 id: 'tabs.item.disableService',
33 defaultMessage: '!!!Disable Service', 41 defaultMessage: '!!!Disable Service',
34 }, 42 },
43 enableService: {
44 id: 'tabs.item.enableService',
45 defaultMessage: '!!!Enable Service',
46 },
35 deleteService: { 47 deleteService: {
36 id: 'tabs.item.deleteService', 48 id: 'tabs.item.deleteService',
37 defaultMessage: '!!!Delete Service', 49 defaultMessage: '!!!Delete Service',
@@ -46,9 +58,11 @@ class TabItem extends Component {
46 shortcutIndex: PropTypes.number.isRequired, 58 shortcutIndex: PropTypes.number.isRequired,
47 reload: PropTypes.func.isRequired, 59 reload: PropTypes.func.isRequired,
48 toggleNotifications: PropTypes.func.isRequired, 60 toggleNotifications: PropTypes.func.isRequired,
61 toggleAudio: PropTypes.func.isRequired,
49 openSettings: PropTypes.func.isRequired, 62 openSettings: PropTypes.func.isRequired,
50 deleteService: PropTypes.func.isRequired, 63 deleteService: PropTypes.func.isRequired,
51 disableService: PropTypes.func.isRequired, 64 disableService: PropTypes.func.isRequired,
65 enableService: PropTypes.func.isRequired,
52 }; 66 };
53 67
54 static contextTypes = { 68 static contextTypes = {
@@ -62,8 +76,10 @@ class TabItem extends Component {
62 shortcutIndex, 76 shortcutIndex,
63 reload, 77 reload,
64 toggleNotifications, 78 toggleNotifications,
79 toggleAudio,
65 deleteService, 80 deleteService,
66 disableService, 81 disableService,
82 enableService,
67 openSettings, 83 openSettings,
68 } = this.props; 84 } = this.props;
69 const { intl } = this.context; 85 const { intl } = this.context;
@@ -90,8 +106,13 @@ class TabItem extends Component {
90 : intl.formatMessage(messages.enableNotifications), 106 : intl.formatMessage(messages.enableNotifications),
91 click: () => toggleNotifications(), 107 click: () => toggleNotifications(),
92 }, { 108 }, {
93 label: intl.formatMessage(messages.disableService), 109 label: service.isMuted
94 click: () => disableService(), 110 ? intl.formatMessage(messages.enableAudio)
111 : intl.formatMessage(messages.disableAudio),
112 click: () => toggleAudio(),
113 }, {
114 label: intl.formatMessage(service.isEnabled ? messages.disableService : messages.enableService),
115 click: () => (service.isEnabled ? disableService() : enableService()),
95 }, { 116 }, {
96 type: 'separator', 117 type: 'separator',
97 }, { 118 }, {
@@ -106,6 +127,7 @@ class TabItem extends Component {
106 'tab-item': true, 127 'tab-item': true,
107 'is-active': service.isActive, 128 'is-active': service.isActive,
108 'has-custom-icon': service.hasCustomIcon, 129 'has-custom-icon': service.hasCustomIcon,
130 'is-disabled': !service.isEnabled,
109 })} 131 })}
110 onClick={clickHandler} 132 onClick={clickHandler}
111 onContextMenu={() => menu.popup(remote.getCurrentWindow())} 133 onContextMenu={() => menu.popup(remote.getCurrentWindow())}
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js
index fdb2c0a59..9da1090b7 100644
--- a/src/components/services/tabs/Tabbar.js
+++ b/src/components/services/tabs/Tabbar.js
@@ -15,6 +15,7 @@ export default class TabBar extends Component {
15 reorder: PropTypes.func.isRequired, 15 reorder: PropTypes.func.isRequired,
16 reload: PropTypes.func.isRequired, 16 reload: PropTypes.func.isRequired,
17 toggleNotifications: PropTypes.func.isRequired, 17 toggleNotifications: PropTypes.func.isRequired,
18 toggleAudio: PropTypes.func.isRequired,
18 deleteService: PropTypes.func.isRequired, 19 deleteService: PropTypes.func.isRequired,
19 updateService: PropTypes.func.isRequired, 20 updateService: PropTypes.func.isRequired,
20 } 21 }
@@ -29,20 +30,30 @@ export default class TabBar extends Component {
29 reorder({ oldIndex, newIndex }); 30 reorder({ oldIndex, newIndex });
30 }; 31 };
31 32
32 disableService = ({ serviceId }) => { 33 shouldPreventSorting = event => event.target.tagName !== 'LI';
34
35 toggleService = ({ serviceId, isEnabled }) => {
33 const { updateService } = this.props; 36 const { updateService } = this.props;
34 37
35 if (serviceId) { 38 if (serviceId) {
36 updateService({ 39 updateService({
37 serviceId, 40 serviceId,
38 serviceData: { 41 serviceData: {
39 isEnabled: false, 42 isEnabled,
40 }, 43 },
41 redirect: false, 44 redirect: false,
42 }); 45 });
43 } 46 }
44 } 47 }
45 48
49 disableService({ serviceId }) {
50 this.toggleService({ serviceId, isEnabled: false });
51 }
52
53 enableService({ serviceId }) {
54 this.toggleService({ serviceId, isEnabled: true });
55 }
56
46 render() { 57 render() {
47 const { 58 const {
48 services, 59 services,
@@ -51,6 +62,7 @@ export default class TabBar extends Component {
51 disableToolTip, 62 disableToolTip,
52 reload, 63 reload,
53 toggleNotifications, 64 toggleNotifications,
65 toggleAudio,
54 deleteService, 66 deleteService,
55 } = this.props; 67 } = this.props;
56 68
@@ -61,10 +73,13 @@ export default class TabBar extends Component {
61 setActive={setActive} 73 setActive={setActive}
62 onSortEnd={this.onSortEnd} 74 onSortEnd={this.onSortEnd}
63 onSortStart={disableToolTip} 75 onSortStart={disableToolTip}
76 shouldCancelStart={this.shouldPreventSorting}
64 reload={reload} 77 reload={reload}
65 toggleNotifications={toggleNotifications} 78 toggleNotifications={toggleNotifications}
79 toggleAudio={toggleAudio}
66 deleteService={deleteService} 80 deleteService={deleteService}
67 disableService={this.disableService} 81 disableService={args => this.disableService(args)}
82 enableService={args => this.enableService(args)}
68 openSettings={openSettings} 83 openSettings={openSettings}
69 distance={20} 84 distance={20}
70 axis="y" 85 axis="y"
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index 3b21a7765..fea8d682d 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -74,7 +74,6 @@ export default class SettingsNavigation extends Component {
74 <Link 74 <Link
75 to="/auth/logout" 75 to="/auth/logout"
76 className="settings-navigation__link" 76 className="settings-navigation__link"
77 activeClassName="is-active"
78 > 77 >
79 {intl.formatMessage(messages.logout)} 78 {intl.formatMessage(messages.logout)}
80 </Link> 79 </Link>
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js
index 02ea04e35..b6ade5da4 100644
--- a/src/components/settings/recipes/RecipesDashboard.js
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -9,6 +9,7 @@ import Infobox from '../../ui/Infobox';
9import RecipeItem from './RecipeItem'; 9import RecipeItem from './RecipeItem';
10import Loader from '../../ui/Loader'; 10import Loader from '../../ui/Loader';
11import Appear from '../../ui/effects/Appear'; 11import Appear from '../../ui/effects/Appear';
12import { FRANZ_SERVICE_REQUEST } from '../../../config';
12 13
13const messages = defineMessages({ 14const messages = defineMessages({
14 headline: { 15 headline: {
@@ -35,6 +36,10 @@ const messages = defineMessages({
35 id: 'settings.recipes.servicesSuccessfulAddedInfo', 36 id: 'settings.recipes.servicesSuccessfulAddedInfo',
36 defaultMessage: '!!!Service successfully added', 37 defaultMessage: '!!!Service successfully added',
37 }, 38 },
39 missingService: {
40 id: 'settings.recipes.missingService',
41 defaultMessage: '!!!Missing a service?',
42 },
38}); 43});
39 44
40@observer 45@observer
@@ -96,33 +101,39 @@ export default class RecipesDashboard extends Component {
96 </Infobox> 101 </Infobox>
97 </Appear> 102 </Appear>
98 )} 103 )}
99 {!searchNeedle && ( 104 {/* {!searchNeedle && ( */}
100 <div className="recipes__navigation"> 105 <div className="recipes__navigation">
101 <Link 106 <Link
102 to="/settings/recipes" 107 to="/settings/recipes"
103 className="badge" 108 className="badge"
104 activeClassName="badge--primary" 109 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`}
105 > 110 onClick={() => resetSearch()}
106 {intl.formatMessage(messages.mostPopularRecipes)} 111 >
107 </Link> 112 {intl.formatMessage(messages.mostPopularRecipes)}
113 </Link>
114 <Link
115 to="/settings/recipes/all"
116 className="badge"
117 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`}
118 onClick={() => resetSearch()}
119 >
120 {intl.formatMessage(messages.allRecipes)}
121 </Link>
122 {devRecipesCount > 0 && (
108 <Link 123 <Link
109 to="/settings/recipes/all" 124 to="/settings/recipes/dev"
110 className="badge" 125 className="badge"
111 activeClassName="badge--primary" 126 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`}
127 onClick={() => resetSearch()}
112 > 128 >
113 {intl.formatMessage(messages.allRecipes)} 129 {intl.formatMessage(messages.devRecipes)} ({devRecipesCount})
114 </Link> 130 </Link>
115 {devRecipesCount > 0 && ( 131 )}
116 <Link 132 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request">
117 to="/settings/recipes/dev" 133 {intl.formatMessage(messages.missingService)} <i className="mdi mdi-open-in-new" />
118 className="badge" 134 </a>
119 activeClassName="badge--primary" 135 </div>
120 > 136 {/* )} */}
121 {intl.formatMessage(messages.devRecipes)} ({devRecipesCount})
122 </Link>
123 )}
124 </div>
125 )}
126 {isLoading ? ( 137 {isLoading ? (
127 <Loader /> 138 <Loader />
128 ) : ( 139 ) : (
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 9b359a78e..36cefe87c 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -61,7 +61,11 @@ const messages = defineMessages({
61 }, 61 },
62 indirectMessageInfo: { 62 indirectMessageInfo: {
63 id: 'settings.service.form.indirectMessageInfo', 63 id: 'settings.service.form.indirectMessageInfo',
64 defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', // eslint-disable-line 64 defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...',
65 },
66 isMutedInfo: {
67 id: 'settings.service.form.isMutedInfo',
68 defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted',
65 }, 69 },
66}); 70});
67 71
@@ -110,7 +114,7 @@ export default class EditServiceForm extends Component {
110 if (recipe.validateUrl && values.customUrl) { 114 if (recipe.validateUrl && values.customUrl) {
111 this.setState({ isValidatingCustomUrl: true }); 115 this.setState({ isValidatingCustomUrl: true });
112 try { 116 try {
113 values.customUrl = normalizeUrl(values.customUrl); 117 values.customUrl = normalizeUrl(values.customUrl, { stripWWW: false });
114 isValid = await recipe.validateUrl(values.customUrl); 118 isValid = await recipe.validateUrl(values.customUrl);
115 } catch (err) { 119 } catch (err) {
116 console.warn('ValidateURL', err); 120 console.warn('ValidateURL', err);
@@ -231,11 +235,15 @@ export default class EditServiceForm extends Component {
231 {recipe.hasIndirectMessages && ( 235 {recipe.hasIndirectMessages && (
232 <div> 236 <div>
233 <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> 237 <Toggle field={form.$('isIndirectMessageBadgeEnabled')} />
234 <p className="settings__indirect-message-help"> 238 <p className="settings__help">
235 {intl.formatMessage(messages.indirectMessageInfo)} 239 {intl.formatMessage(messages.indirectMessageInfo)}
236 </p> 240 </p>
237 </div> 241 </div>
238 )} 242 )}
243 <Toggle field={form.$('isMuted')} />
244 <p className="settings__help">
245 {intl.formatMessage(messages.isMutedInfo)}
246 </p>
239 <Toggle field={form.$('isEnabled')} /> 247 <Toggle field={form.$('isEnabled')} />
240 </div> 248 </div>
241 {recipe.message && ( 249 {recipe.message && (
diff --git a/src/components/settings/services/ServiceItem.js b/src/components/settings/services/ServiceItem.js
index 20d8581d0..9743315b0 100644
--- a/src/components/settings/services/ServiceItem.js
+++ b/src/components/settings/services/ServiceItem.js
@@ -16,6 +16,10 @@ const messages = defineMessages({
16 id: 'settings.services.tooltip.notificationsDisabled', 16 id: 'settings.services.tooltip.notificationsDisabled',
17 defaultMessage: '!!!Notifications are disabled', 17 defaultMessage: '!!!Notifications are disabled',
18 }, 18 },
19 tooltipIsMuted: {
20 id: 'settings.services.tooltip.isMuted',
21 defaultMessage: '!!!All sounds are muted',
22 },
19}); 23});
20 24
21@observer 25@observer
@@ -66,6 +70,17 @@ export default class ServiceItem extends Component {
66 className="service-table__column-info" 70 className="service-table__column-info"
67 onClick={goToServiceForm} 71 onClick={goToServiceForm}
68 > 72 >
73 {service.isMuted && (
74 <span
75 className="mdi mdi-bell-off"
76 data-tip={intl.formatMessage(messages.tooltipIsMuted)}
77 />
78 )}
79 </td>
80 <td
81 className="service-table__column-info"
82 onClick={goToServiceForm}
83 >
69 {!service.isEnabled && ( 84 {!service.isEnabled && (
70 <span 85 <span
71 className="mdi mdi-power" 86 className="mdi mdi-power"
@@ -85,13 +100,6 @@ export default class ServiceItem extends Component {
85 )} 100 )}
86 <ReactTooltip place="top" type="dark" effect="solid" /> 101 <ReactTooltip place="top" type="dark" effect="solid" />
87 </td> 102 </td>
88 {/* <td className="service-table__column-action">
89 <input
90 type="checkbox"
91 onChange={toggleAction}
92 checked={service.isEnabled}
93 />
94 </td> */}
95 </tr> 103 </tr>
96 ); 104 );
97 } 105 }
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 5675fecf4..4ce9b7ab2 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -9,6 +9,8 @@ import Button from '../../ui/Button';
9import Toggle from '../../ui/Toggle'; 9import Toggle from '../../ui/Toggle';
10import Select from '../../ui/Select'; 10import Select from '../../ui/Select';
11 11
12import { FRANZ_TRANSLATION } from '../../../config';
13
12const messages = defineMessages({ 14const messages = defineMessages({
13 headline: { 15 headline: {
14 id: 'settings.app.headline', 16 id: 'settings.app.headline',
@@ -26,6 +28,18 @@ const messages = defineMessages({
26 id: 'settings.app.headlineUpdates', 28 id: 'settings.app.headlineUpdates',
27 defaultMessage: '!!!Updates', 29 defaultMessage: '!!!Updates',
28 }, 30 },
31 headlineAppearance: {
32 id: 'settings.app.headlineAppearance',
33 defaultMessage: '!!!Appearance',
34 },
35 headlineAdvanced: {
36 id: 'settings.app.headlineAdvanced',
37 defaultMessage: '!!!Advanced',
38 },
39 translationHelp: {
40 id: 'settings.app.translationHelp',
41 defaultMessage: '!!!Help us to translate Franz into your language.',
42 },
29 buttonSearchForUpdate: { 43 buttonSearchForUpdate: {
30 id: 'settings.app.buttonSearchForUpdate', 44 id: 'settings.app.buttonSearchForUpdate',
31 defaultMessage: '!!!Check for updates', 45 defaultMessage: '!!!Check for updates',
@@ -50,6 +64,10 @@ const messages = defineMessages({
50 id: 'settings.app.currentVersion', 64 id: 'settings.app.currentVersion',
51 defaultMessage: '!!!Current version:', 65 defaultMessage: '!!!Current version:',
52 }, 66 },
67 restartRequired: {
68 id: 'settings.app.restartRequired',
69 defaultMessage: '!!!Changes require restart',
70 },
53}); 71});
54 72
55@observer 73@observer
@@ -112,16 +130,38 @@ export default class EditSettingsForm extends Component {
112 onChange={e => this.submit(e)} 130 onChange={e => this.submit(e)}
113 id="form" 131 id="form"
114 > 132 >
115 <h2>{intl.formatMessage(messages.headlineGeneral)}</h2> 133 {/* General */}
134 <h2 id="general">{intl.formatMessage(messages.headlineGeneral)}</h2>
116 <Toggle field={form.$('autoLaunchOnStart')} /> 135 <Toggle field={form.$('autoLaunchOnStart')} />
117 <Toggle field={form.$('runInBackground')} /> 136 <Toggle field={form.$('runInBackground')} />
118 <Toggle field={form.$('enableSystemTray')} /> 137 <Toggle field={form.$('enableSystemTray')} />
119 {process.platform === 'win32' && ( 138 {process.platform === 'win32' && (
120 <Toggle field={form.$('minimizeToSystemTray')} /> 139 <Toggle field={form.$('minimizeToSystemTray')} />
121 )} 140 )}
122 <h2>{intl.formatMessage(messages.headlineLanguage)}</h2> 141
142 {/* Appearance */}
143 <h2 id="apperance">{intl.formatMessage(messages.headlineAppearance)}</h2>
144 <Toggle field={form.$('showDisabledServices')} />
145
146 {/* Language */}
147 <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2>
123 <Select field={form.$('locale')} showLabel={false} /> 148 <Select field={form.$('locale')} showLabel={false} />
124 <h2>{intl.formatMessage(messages.headlineUpdates)}</h2> 149 <a
150 href={FRANZ_TRANSLATION}
151 target="_blank"
152 className="link"
153 >
154 {intl.formatMessage(messages.translationHelp)} <i className="mdi mdi-open-in-new" />
155 </a>
156
157 {/* Advanced */}
158 <h2 id="advanced">{intl.formatMessage(messages.headlineAdvanced)}</h2>
159 <Toggle field={form.$('enableSpellchecking')} />
160 <p className="settings__help">{intl.formatMessage(messages.restartRequired)}</p>
161 {/* <Select field={form.$('spellcheckingLanguage')} /> */}
162
163 {/* Updates */}
164 <h2 id="updates">{intl.formatMessage(messages.headlineUpdates)}</h2>
125 {updateIsReadyToInstall ? ( 165 {updateIsReadyToInstall ? (
126 <Button 166 <Button
127 label={intl.formatMessage(messages.buttonInstallUpdate)} 167 label={intl.formatMessage(messages.buttonInstallUpdate)}
diff --git a/src/components/ui/AppLoader.js b/src/components/ui/AppLoader.js
index 64a212969..ac3cdcb05 100644
--- a/src/components/ui/AppLoader.js
+++ b/src/components/ui/AppLoader.js
@@ -8,7 +8,7 @@ export default function () {
8 <div className="app-loader"> 8 <div className="app-loader">
9 <Appear> 9 <Appear>
10 <h1 className="app-loader__title">Franz</h1> 10 <h1 className="app-loader__title">Franz</h1>
11 <Loader /> 11 <Loader color="#FFF" />
12 </Appear> 12 </Appear>
13 </div> 13 </div>
14 ); 14 );
diff --git a/src/components/ui/InfoBar.js b/src/components/ui/InfoBar.js
index aea2bd888..84a5f1446 100644
--- a/src/components/ui/InfoBar.js
+++ b/src/components/ui/InfoBar.js
@@ -61,10 +61,13 @@ export default class InfoBar extends Component {
61 [`${className}`]: true, 61 [`${className}`]: true,
62 })} 62 })}
63 > 63 >
64 <div onClick={onClick} className="info-bar__content"> 64 <div className="info-bar__content">
65 {children} 65 {children}
66 {ctaLabel && ( 66 {ctaLabel && (
67 <button className="info-bar__cta"> 67 <button
68 className="info-bar__cta"
69 onClick={onClick}
70 >
68 <Loader 71 <Loader
69 loaded={!ctaLoading} 72 loaded={!ctaLoading}
70 lines={10} 73 lines={10}
diff --git a/src/components/ui/Loader.js b/src/components/ui/Loader.js
index e4fbd96a2..f73296bb6 100644
--- a/src/components/ui/Loader.js
+++ b/src/components/ui/Loader.js
@@ -9,12 +9,14 @@ export default class LoaderComponent extends Component {
9 children: oneOrManyChildElements, 9 children: oneOrManyChildElements,
10 loaded: PropTypes.bool, 10 loaded: PropTypes.bool,
11 className: PropTypes.string, 11 className: PropTypes.string,
12 color: PropTypes.string,
12 }; 13 };
13 14
14 static defaultProps = { 15 static defaultProps = {
15 children: null, 16 children: null,
16 loaded: false, 17 loaded: false,
17 className: '', 18 className: '',
19 color: '#373a3c',
18 }; 20 };
19 21
20 render() { 22 render() {
@@ -22,6 +24,7 @@ export default class LoaderComponent extends Component {
22 children, 24 children,
23 loaded, 25 loaded,
24 className, 26 className,
27 color,
25 } = this.props; 28 } = this.props;
26 29
27 return ( 30 return (
@@ -30,7 +33,7 @@ export default class LoaderComponent extends Component {
30 // lines={10} 33 // lines={10}
31 width={4} 34 width={4}
32 scale={0.6} 35 scale={0.6}
33 color="#373a3c" 36 color={color}
34 component="span" 37 component="span"
35 className={className} 38 className={className}
36 > 39 >
diff --git a/src/components/ui/Subscription.js b/src/components/ui/Subscription.js
index fe0925a26..8bff72095 100644
--- a/src/components/ui/Subscription.js
+++ b/src/components/ui/Subscription.js
@@ -93,6 +93,10 @@ const messages = defineMessages({
93 id: 'subscription.mining.moreInformation', 93 id: 'subscription.mining.moreInformation',
94 defaultMessage: '!!!Get more information about this plan', 94 defaultMessage: '!!!Get more information about this plan',
95 }, 95 },
96 euTaxInfo: {
97 id: 'subscription.euTaxInfo',
98 defaultMessage: '!!!EU residents: local sales tax may apply',
99 },
96}); 100});
97 101
98@observer 102@observer
@@ -144,14 +148,18 @@ export default class SubscriptionForm extends Component {
144 label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'year') 148 label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'year')
145 ? `${this.props.plan.year.price} / ${intl.formatMessage(messages.typeYearly)}` 149 ? `${this.props.plan.year.price} / ${intl.formatMessage(messages.typeYearly)}`
146 : 'yearly'}`, 150 : 'yearly'}`,
147 }, {
148 value: 'mining',
149 label: intl.formatMessage(messages.typeMining),
150 }], 151 }],
151 }, 152 },
152 }, 153 },
153 }; 154 };
154 155
156 if (this.props.plan.miner) {
157 form.fields.paymentTier.options.push({
158 value: 'mining',
159 label: intl.formatMessage(messages.typeMining),
160 });
161 }
162
155 if (this.props.showSkipOption) { 163 if (this.props.showSkipOption) {
156 form.fields.paymentTier.options.unshift({ 164 form.fields.paymentTier.options.unshift({
157 value: 'skip', 165 value: 'skip',
@@ -259,6 +267,11 @@ export default class SubscriptionForm extends Component {
259 onClick={() => handlePayment(this.form.$('paymentTier').value)} 267 onClick={() => handlePayment(this.form.$('paymentTier').value)}
260 /> 268 />
261 )} 269 )}
270 {this.form.$('paymentTier').value !== 'skip' && this.form.$('paymentTier').value !== 'mining' && (
271 <p className="legal">
272 {intl.formatMessage(messages.euTaxInfo)}
273 </p>
274 )}
262 </Loader> 275 </Loader>
263 ); 276 );
264 } 277 }