aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/api/LocalApi.js8
-rw-r--r--src/api/server/LocalApi.js19
-rw-r--r--src/components/settings/account/AccountDashboard.js8
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js25
-rw-r--r--src/components/settings/services/EditServiceForm.js45
-rw-r--r--src/components/settings/settings/EditSettingsForm.js20
-rw-r--r--src/components/subscription/SubscriptionForm.js3
-rw-r--r--src/components/ui/PremiumFeatureContainer/index.js18
-rw-r--r--src/config.js7
-rw-r--r--src/containers/settings/EditServiceScreen.js71
-rw-r--r--src/containers/settings/EditSettingsScreen.js1
-rw-r--r--src/containers/subscription/SubscriptionFormScreen.js1
-rw-r--r--src/electron/Settings.js24
-rw-r--r--src/electron/ipc-api/appIndicator.js2
-rw-r--r--src/electron/ipc-api/settings.js10
-rw-r--r--src/features/delayApp/index.js4
-rw-r--r--src/features/serviceProxy/index.js56
-rw-r--r--src/features/spellchecker/index.js3
-rw-r--r--src/i18n/locales/en-US.json6
-rw-r--r--src/index.js34
-rw-r--r--src/models/Service.js6
-rw-r--r--src/models/Settings.js11
-rw-r--r--src/stores/FeaturesStore.js2
-rw-r--r--src/stores/ServicesStore.js15
-rw-r--r--src/stores/SettingsStore.js81
-rw-r--r--src/styles/settings.scss5
-rw-r--r--src/webview/plugin.js8
27 files changed, 381 insertions, 112 deletions
diff --git a/src/api/LocalApi.js b/src/api/LocalApi.js
index 741917104..e2a46874a 100644
--- a/src/api/LocalApi.js
+++ b/src/api/LocalApi.js
@@ -4,12 +4,12 @@ export default class LocalApi {
4 this.local = local; 4 this.local = local;
5 } 5 }
6 6
7 getAppSettings() { 7 getAppSettings(type) {
8 return this.local.getAppSettings(); 8 return this.local.getAppSettings(type);
9 } 9 }
10 10
11 updateAppSettings(data) { 11 updateAppSettings(type, data) {
12 return this.local.updateAppSettings(data); 12 return this.local.updateAppSettings(type, data);
13 } 13 }
14 14
15 getAppCacheSize() { 15 getAppCacheSize() {
diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js
index 4814bba66..ab1604a27 100644
--- a/src/api/server/LocalApi.js
+++ b/src/api/server/LocalApi.js
@@ -9,20 +9,23 @@ const { session } = remote;
9 9
10export default class LocalApi { 10export default class LocalApi {
11 // Settings 11 // Settings
12 getAppSettings() { 12 getAppSettings(type) {
13 return new Promise((resolve) => { 13 return new Promise((resolve) => {
14 ipcRenderer.once('appSettings', (event, data) => { 14 ipcRenderer.once('appSettings', (event, resp) => {
15 debug('LocalApi::getAppSettings resolves', data); 15 debug('LocalApi::getAppSettings resolves', resp.type, resp.data);
16 resolve(data); 16 resolve(resp);
17 }); 17 });
18 18
19 ipcRenderer.send('getAppSettings'); 19 ipcRenderer.send('getAppSettings', type);
20 }); 20 });
21 } 21 }
22 22
23 async updateAppSettings(data) { 23 async updateAppSettings(type, data) {
24 debug('LocalApi::updateAppSettings resolves', data); 24 debug('LocalApi::updateAppSettings resolves', type, data);
25 ipcRenderer.send('updateAppSettings', data); 25 ipcRenderer.send('updateAppSettings', {
26 type,
27 data,
28 });
26 } 29 }
27 30
28 // Services 31 // Services
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index ede519fd6..06c7074dd 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -180,11 +180,9 @@ export default @observer class AccountDashboard extends Component {
180 <span className="badge badge--success">{intl.formatMessage(messages.accountTypeEnterprise)}</span> 180 <span className="badge badge--success">{intl.formatMessage(messages.accountTypeEnterprise)}</span>
181 )} 181 )}
182 </div> 182 </div>
183 {!user.isSSO && ( 183 <Link to="/settings/user/edit" className="button">
184 <Link to="/settings/user/edit" className="button"> 184 {intl.formatMessage(messages.accountEditButton)}
185 {intl.formatMessage(messages.accountEditButton)} 185 </Link>
186 </Link>
187 )}
188 {user.emailValidated} 186 {user.emailValidated}
189 </div> 187 </div>
190 </div> 188 </div>
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index d8b410aaf..b86d94ac7 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -43,20 +43,17 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
43 43
44 render() { 44 render() {
45 const { serviceCount } = this.props; 45 const { serviceCount } = this.props;
46 const { features } = this.props.stores.features;
47 const { intl } = this.context; 46 const { intl } = this.context;
48 47
49 return ( 48 return (
50 <div className="settings-navigation"> 49 <div className="settings-navigation">
51 {features.userCanManageServices && ( 50 <Link
52 <Link 51 to="/settings/recipes"
53 to="/settings/recipes" 52 className="settings-navigation__link"
54 className="settings-navigation__link" 53 activeClassName="is-active"
55 activeClassName="is-active" 54 >
56 > 55 {intl.formatMessage(messages.availableServices)}
57 {intl.formatMessage(messages.availableServices)} 56 </Link>
58 </Link>
59 )}
60 <Link 57 <Link
61 to="/settings/services" 58 to="/settings/services"
62 className="settings-navigation__link" 59 className="settings-navigation__link"
@@ -97,11 +94,3 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
97 } 94 }
98} 95}
99 96
100SettingsNavigation.wrappedComponent.propTypes = {
101 stores: PropTypes.shape({
102 features: PropTypes.shape({
103 features: PropTypes.object.isRequired,
104 }).isRequired,
105 }).isRequired,
106};
107
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 777a95fcf..47772efae 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -15,6 +15,8 @@ import Toggle from '../../ui/Toggle';
15import Button from '../../ui/Button'; 15import Button from '../../ui/Button';
16import ImageUpload from '../../ui/ImageUpload'; 16import ImageUpload from '../../ui/ImageUpload';
17 17
18import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
19
18const messages = defineMessages({ 20const messages = defineMessages({
19 saveService: { 21 saveService: {
20 id: 'settings.service.form.saveButton', 22 id: 'settings.service.form.saveButton',
@@ -92,6 +94,14 @@ const messages = defineMessages({
92 id: 'settings.service.form.iconUpload', 94 id: 'settings.service.form.iconUpload',
93 defaultMessage: '!!!Drop your image, or click here', 95 defaultMessage: '!!!Drop your image, or click here',
94 }, 96 },
97 headlineProxy: {
98 id: 'settings.service.form.proxy.headline',
99 defaultMessage: '!!!Proxy Settings',
100 },
101 proxyInfo: {
102 id: 'settings.service.form.proxy.info',
103 defaultMessage: '!!!Proxy settings will not be synchronized with the Franz servers.',
104 },
95}); 105});
96 106
97export default @observer class EditServiceForm extends Component { 107export default @observer class EditServiceForm extends Component {
@@ -106,13 +116,14 @@ export default @observer class EditServiceForm extends Component {
106 return null; 116 return null;
107 }, 117 },
108 user: PropTypes.instanceOf(User).isRequired, 118 user: PropTypes.instanceOf(User).isRequired,
109 userCanManageServices: PropTypes.bool.isRequired,
110 action: PropTypes.string.isRequired, 119 action: PropTypes.string.isRequired,
111 form: PropTypes.instanceOf(Form).isRequired, 120 form: PropTypes.instanceOf(Form).isRequired,
112 onSubmit: PropTypes.func.isRequired, 121 onSubmit: PropTypes.func.isRequired,
113 onDelete: PropTypes.func.isRequired, 122 onDelete: PropTypes.func.isRequired,
114 isSaving: PropTypes.bool.isRequired, 123 isSaving: PropTypes.bool.isRequired,
115 isDeleting: PropTypes.bool.isRequired, 124 isDeleting: PropTypes.bool.isRequired,
125 isProxyFeatureEnabled: PropTypes.bool.isRequired,
126 isProxyFeaturePremiumFeature: PropTypes.bool.isRequired,
116 }; 127 };
117 128
118 static defaultProps = { 129 static defaultProps = {
@@ -169,11 +180,12 @@ export default @observer class EditServiceForm extends Component {
169 service, 180 service,
170 action, 181 action,
171 user, 182 user,
172 userCanManageServices,
173 form, 183 form,
174 isSaving, 184 isSaving,
175 isDeleting, 185 isDeleting,
176 onDelete, 186 onDelete,
187 isProxyFeatureEnabled,
188 isProxyFeaturePremiumFeature,
177 } = this.props; 189 } = this.props;
178 const { intl } = this.context; 190 const { intl } = this.context;
179 191
@@ -318,6 +330,33 @@ export default @observer class EditServiceForm extends Component {
318 /> 330 />
319 </div> 331 </div>
320 </div> 332 </div>
333
334 {isProxyFeatureEnabled && (
335 <PremiumFeatureContainer condition={isProxyFeaturePremiumFeature}>
336 <div className="settings__settings-group">
337 <h3>
338 {intl.formatMessage(messages.headlineProxy)}
339 <span className="badge badge--success">beta</span>
340 </h3>
341 <Toggle field={form.$('proxy.isEnabled')} />
342 {form.$('proxy.isEnabled').value && (
343 <div>
344 <Input field={form.$('proxy.host')} />
345 <Input field={form.$('proxy.user')} />
346 <Input
347 field={form.$('proxy.password')}
348 showPasswordToggle
349 />
350 <p>
351 <span className="mdi mdi-information" />
352 {intl.formatMessage(messages.proxyInfo)}
353 </p>
354 </div>
355 )}
356 </div>
357 </PremiumFeatureContainer>
358 )}
359
321 {recipe.message && ( 360 {recipe.message && (
322 <p className="settings__message"> 361 <p className="settings__message">
323 <span className="mdi mdi-information" /> 362 <span className="mdi mdi-information" />
@@ -328,7 +367,7 @@ export default @observer class EditServiceForm extends Component {
328 </div> 367 </div>
329 <div className="settings__controls"> 368 <div className="settings__controls">
330 {/* Delete Button */} 369 {/* Delete Button */}
331 {action === 'edit' && userCanManageServices && deleteButton} 370 {action === 'edit' && deleteButton}
332 371
333 {/* Save Button */} 372 {/* Save Button */}
334 {isSaving || isValidatingCustomUrl ? ( 373 {isSaving || isValidatingCustomUrl ? (
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index b87c11fc4..280449ead 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -96,7 +96,6 @@ export default @observer class EditSettingsForm extends Component {
96 isClearingAllCache: PropTypes.bool.isRequired, 96 isClearingAllCache: PropTypes.bool.isRequired,
97 onClearAllCache: PropTypes.func.isRequired, 97 onClearAllCache: PropTypes.func.isRequired,
98 cacheSize: PropTypes.string.isRequired, 98 cacheSize: PropTypes.string.isRequired,
99 isPremiumUser: PropTypes.bool.isRequired,
100 isSpellcheckerPremiumFeature: PropTypes.bool.isRequired, 99 isSpellcheckerPremiumFeature: PropTypes.bool.isRequired,
101 }; 100 };
102 101
@@ -127,7 +126,6 @@ export default @observer class EditSettingsForm extends Component {
127 isClearingAllCache, 126 isClearingAllCache,
128 onClearAllCache, 127 onClearAllCache,
129 cacheSize, 128 cacheSize,
130 isPremiumUser,
131 isSpellcheckerPremiumFeature, 129 isSpellcheckerPremiumFeature,
132 } = this.props; 130 } = this.props;
133 const { intl } = this.context; 131 const { intl } = this.context;
@@ -180,16 +178,14 @@ export default @observer class EditSettingsForm extends Component {
180 178
181 {/* Advanced */} 179 {/* Advanced */}
182 <h2 id="advanced">{intl.formatMessage(messages.headlineAdvanced)}</h2> 180 <h2 id="advanced">{intl.formatMessage(messages.headlineAdvanced)}</h2>
183 {!isPremiumUser && isSpellcheckerPremiumFeature ? ( 181 <PremiumFeatureContainer
184 <PremiumFeatureContainer> 182 condition={isSpellcheckerPremiumFeature}
185 <Toggle 183 >
186 field={form.$('enableSpellchecking')} 184 <Toggle
187 disabled 185 field={form.$('enableSpellchecking')}
188 /> 186 disabled
189 </PremiumFeatureContainer> 187 />
190 ) : ( 188 </PremiumFeatureContainer>
191 <Toggle field={form.$('enableSpellchecking')} />
192 )}
193 <Toggle field={form.$('enableGPUAcceleration')} /> 189 <Toggle field={form.$('enableGPUAcceleration')} />
194 <p className="settings__help">{intl.formatMessage(messages.enableGPUAccelerationInfo)}</p> 190 <p className="settings__help">{intl.formatMessage(messages.enableGPUAccelerationInfo)}</p>
195 {/* <Select field={form.$('spellcheckingLanguage')} /> */} 191 {/* <Select field={form.$('spellcheckingLanguage')} /> */}
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js
index 8b8fd4f18..12965e307 100644
--- a/src/components/subscription/SubscriptionForm.js
+++ b/src/components/subscription/SubscriptionForm.js
@@ -40,9 +40,6 @@ const messages = defineMessages({
40 id: 'subscription.features.onpremise.mattermost', 40 id: 'subscription.features.onpremise.mattermost',
41 defaultMessage: '!!!Add on-premise/hosted services like Mattermost', 41 defaultMessage: '!!!Add on-premise/hosted services like Mattermost',
42 }, 42 },
43 encryptedSync: {
44 id: 'subscription.features.encryptedSync',
45 defaultMessage: '!!!Encrypted session synchronization',
46 noInterruptions: { 43 noInterruptions: {
47 id: 'subscription.features.noInterruptions', 44 id: 'subscription.features.noInterruptions',
48 defaultMessage: '!!!No app delays & nagging to upgrade license', 45 defaultMessage: '!!!No app delays & nagging to upgrade license',
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js
index 113fe2221..73984be94 100644
--- a/src/components/ui/PremiumFeatureContainer/index.js
+++ b/src/components/ui/PremiumFeatureContainer/index.js
@@ -6,6 +6,8 @@ import injectSheet from 'react-jss';
6 6
7import { oneOrManyChildElements } from '../../../prop-types'; 7import { oneOrManyChildElements } from '../../../prop-types';
8 8
9import UserStore from '../../../stores/UserStore';
10
9import styles from './styles'; 11import styles from './styles';
10 12
11const messages = defineMessages({ 13const messages = defineMessages({
@@ -15,9 +17,14 @@ const messages = defineMessages({
15 }, 17 },
16}); 18});
17 19
18export default @inject('actions') @injectSheet(styles) @observer class PremiumFeatureContainer extends Component { 20export default @inject('stores', 'actions') @injectSheet(styles) @observer class PremiumFeatureContainer extends Component {
19 static propTypes = { 21 static propTypes = {
20 classes: PropTypes.object.isRequired, 22 classes: PropTypes.object.isRequired,
23 condition: PropTypes.bool,
24 };
25
26 static defaultProps = {
27 condition: true,
21 }; 28 };
22 29
23 static contextTypes = { 30 static contextTypes = {
@@ -29,11 +36,13 @@ export default @inject('actions') @injectSheet(styles) @observer class PremiumFe
29 classes, 36 classes,
30 children, 37 children,
31 actions, 38 actions,
39 condition,
40 stores,
32 } = this.props; 41 } = this.props;
33 42
34 const { intl } = this.context; 43 const { intl } = this.context;
35 44
36 return ( 45 return !stores.user.data.isPremium && !!condition ? (
37 <div className={classes.container}> 46 <div className={classes.container}>
38 <div className={classes.titleContainer}> 47 <div className={classes.titleContainer}>
39 <p className={classes.title}>Premium Feature</p> 48 <p className={classes.title}>Premium Feature</p>
@@ -49,12 +58,15 @@ export default @inject('actions') @injectSheet(styles) @observer class PremiumFe
49 {children} 58 {children}
50 </div> 59 </div>
51 </div> 60 </div>
52 ); 61 ) : children;
53 } 62 }
54} 63}
55 64
56PremiumFeatureContainer.wrappedComponent.propTypes = { 65PremiumFeatureContainer.wrappedComponent.propTypes = {
57 children: oneOrManyChildElements.isRequired, 66 children: oneOrManyChildElements.isRequired,
67 stores: PropTypes.shape({
68 user: PropTypes.instanceOf(UserStore).isRequired,
69 }).isRequired,
58 actions: PropTypes.shape({ 70 actions: PropTypes.shape({
59 ui: PropTypes.shape({ 71 ui: PropTypes.shape({
60 openSettings: PropTypes.func.isRequired, 72 openSettings: PropTypes.func.isRequired,
diff --git a/src/config.js b/src/config.js
index ebffacceb..b5702a202 100644
--- a/src/config.js
+++ b/src/config.js
@@ -29,4 +29,9 @@ export const DEFAULT_APP_SETTINGS = {
29export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-service-request'; 29export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-service-request';
30export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate'; 30export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate';
31 31
32export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config', 'settings.json'); 32export const FILE_SYSTEM_SETTINGS_TYPES = [
33 'app',
34 'proxy',
35];
36
37export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config');
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
index 17d727642..639e8b070 100644
--- a/src/containers/settings/EditServiceScreen.js
+++ b/src/containers/settings/EditServiceScreen.js
@@ -6,8 +6,8 @@ import { defineMessages, intlShape } from 'react-intl';
6import UserStore from '../../stores/UserStore'; 6import UserStore from '../../stores/UserStore';
7import RecipesStore from '../../stores/RecipesStore'; 7import RecipesStore from '../../stores/RecipesStore';
8import ServicesStore from '../../stores/ServicesStore'; 8import ServicesStore from '../../stores/ServicesStore';
9import FeaturesStore from '../../stores/FeaturesStore';
10import SettingsStore from '../../stores/SettingsStore'; 9import SettingsStore from '../../stores/SettingsStore';
10import FeaturesStore from '../../stores/FeaturesStore';
11import Form from '../../lib/Form'; 11import Form from '../../lib/Form';
12import { gaPage } from '../../lib/analytics'; 12import { gaPage } from '../../lib/analytics';
13 13
@@ -15,6 +15,8 @@ import ServiceError from '../../components/settings/services/ServiceError';
15import EditServiceForm from '../../components/settings/services/EditServiceForm'; 15import EditServiceForm from '../../components/settings/services/EditServiceForm';
16import { required, url, oneRequired } from '../../helpers/validation-helpers'; 16import { required, url, oneRequired } from '../../helpers/validation-helpers';
17 17
18import { config as proxyFeature } from '../../features/serviceProxy';
19
18const messages = defineMessages({ 20const messages = defineMessages({
19 name: { 21 name: {
20 id: 'settings.service.form.name', 22 id: 'settings.service.form.name',
@@ -56,6 +58,22 @@ const messages = defineMessages({
56 id: 'settings.service.form.enableDarkMode', 58 id: 'settings.service.form.enableDarkMode',
57 defaultMessage: '!!!Enable Dark Mode', 59 defaultMessage: '!!!Enable Dark Mode',
58 }, 60 },
61 enableProxy: {
62 id: 'settings.service.form.proxy.isEnabled',
63 defaultMessage: '!!!Use Proxy',
64 },
65 proxyHost: {
66 id: 'settings.service.form.proxy.host',
67 defaultMessage: '!!!Proxy Host/IP',
68 },
69 proxyUser: {
70 id: 'settings.service.form.proxy.user',
71 defaultMessage: '!!!User',
72 },
73 proxyPassword: {
74 id: 'settings.service.form.proxy.password',
75 defaultMessage: '!!!Password',
76 },
59}); 77});
60 78
61export default @inject('stores', 'actions') @observer class EditServiceScreen extends Component { 79export default @inject('stores', 'actions') @observer class EditServiceScreen extends Component {
@@ -82,7 +100,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
82 } 100 }
83 } 101 }
84 102
85 prepareForm(recipe, service, userCanManageServices) { 103 prepareForm(recipe, service, proxy) {
86 const { intl } = this.context; 104 const { intl } = this.context;
87 const config = { 105 const config = {
88 fields: { 106 fields: {
@@ -128,7 +146,6 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
128 if (recipe.hasTeamId) { 146 if (recipe.hasTeamId) {
129 Object.assign(config.fields, { 147 Object.assign(config.fields, {
130 team: { 148 team: {
131 disabled: !userCanManageServices,
132 label: intl.formatMessage(messages.team), 149 label: intl.formatMessage(messages.team),
133 placeholder: intl.formatMessage(messages.team), 150 placeholder: intl.formatMessage(messages.team),
134 value: service.team, 151 value: service.team,
@@ -140,7 +157,6 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
140 if (recipe.hasCustomUrl) { 157 if (recipe.hasCustomUrl) {
141 Object.assign(config.fields, { 158 Object.assign(config.fields, {
142 customUrl: { 159 customUrl: {
143 disabled: !userCanManageServices,
144 label: intl.formatMessage(messages.customUrl), 160 label: intl.formatMessage(messages.customUrl),
145 placeholder: 'https://', 161 placeholder: 'https://',
146 value: service.customUrl, 162 value: service.customUrl,
@@ -175,6 +191,40 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
175 }); 191 });
176 } 192 }
177 193
194 if (proxy.isEnabled) {
195 const serviceProxyConfig = this.props.stores.settings.proxy[service.id] || {};
196
197 Object.assign(config.fields, {
198 proxy: {
199 name: 'proxy',
200 label: 'proxy',
201 fields: {
202 isEnabled: {
203 label: intl.formatMessage(messages.enableProxy),
204 value: serviceProxyConfig.isEnabled,
205 default: false,
206 },
207 host: {
208 label: intl.formatMessage(messages.proxyHost),
209 value: serviceProxyConfig.host,
210 default: '',
211 },
212 user: {
213 label: intl.formatMessage(messages.proxyUser),
214 value: serviceProxyConfig.user,
215 default: '',
216 },
217 password: {
218 label: intl.formatMessage(messages.proxyPassword),
219 value: serviceProxyConfig.password,
220 default: '',
221 type: 'password',
222 },
223 },
224 },
225 });
226 }
227
178 return new Form(config); 228 return new Form(config);
179 } 229 }
180 230
@@ -192,7 +242,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
192 } 242 }
193 243
194 render() { 244 render() {
195 const { recipes, services, user, features } = this.props.stores; 245 const { recipes, services, user } = this.props.stores;
196 const { action } = this.props.router.params; 246 const { action } = this.props.router.params;
197 247
198 let recipe; 248 let recipe;
@@ -227,8 +277,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
227 ); 277 );
228 } 278 }
229 279
230 const userCanManageServices = features.features.userCanManageServices; 280 const form = this.prepareForm(recipe, service, proxyFeature);
231 const form = this.prepareForm(recipe, service, userCanManageServices);
232 281
233 return ( 282 return (
234 <EditServiceForm 283 <EditServiceForm
@@ -236,13 +285,14 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
236 recipe={recipe} 285 recipe={recipe}
237 service={service} 286 service={service}
238 user={user.data} 287 user={user.data}
239 userCanManageServices={userCanManageServices}
240 form={form} 288 form={form}
241 status={services.actionStatus} 289 status={services.actionStatus}
242 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting} 290 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting}
243 isDeleting={services.deleteServiceRequest.isExecuting} 291 isDeleting={services.deleteServiceRequest.isExecuting}
244 onSubmit={d => this.onSubmit(d)} 292 onSubmit={d => this.onSubmit(d)}
245 onDelete={() => this.deleteService()} 293 onDelete={() => this.deleteService()}
294 isProxyFeatureEnabled={proxyFeature.isEnabled}
295 isProxyFeaturePremiumFeature={proxyFeature.isPremium}
246 /> 296 />
247 ); 297 );
248 } 298 }
@@ -253,8 +303,8 @@ EditServiceScreen.wrappedComponent.propTypes = {
253 user: PropTypes.instanceOf(UserStore).isRequired, 303 user: PropTypes.instanceOf(UserStore).isRequired,
254 recipes: PropTypes.instanceOf(RecipesStore).isRequired, 304 recipes: PropTypes.instanceOf(RecipesStore).isRequired,
255 services: PropTypes.instanceOf(ServicesStore).isRequired, 305 services: PropTypes.instanceOf(ServicesStore).isRequired,
256 features: PropTypes.instanceOf(FeaturesStore).isRequired,
257 settings: PropTypes.instanceOf(SettingsStore).isRequired, 306 settings: PropTypes.instanceOf(SettingsStore).isRequired,
307 features: PropTypes.instanceOf(FeaturesStore).isRequired,
258 }).isRequired, 308 }).isRequired,
259 router: PropTypes.shape({ 309 router: PropTypes.shape({
260 params: PropTypes.shape({ 310 params: PropTypes.shape({
@@ -267,5 +317,8 @@ EditServiceScreen.wrappedComponent.propTypes = {
267 updateService: PropTypes.func.isRequired, 317 updateService: PropTypes.func.isRequired,
268 deleteService: PropTypes.func.isRequired, 318 deleteService: PropTypes.func.isRequired,
269 }).isRequired, 319 }).isRequired,
320 // settings: PropTypes.shape({
321 // update: PropTypes.func.isRequred,
322 // }).isRequired,
270 }).isRequired, 323 }).isRequired,
271}; 324};
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
index 2fb6bed5f..7da009c8b 100644
--- a/src/containers/settings/EditSettingsScreen.js
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -219,7 +219,6 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
219 cacheSize={cacheSize} 219 cacheSize={cacheSize}
220 isClearingAllCache={isClearingAllCache} 220 isClearingAllCache={isClearingAllCache}
221 onClearAllCache={clearAllCache} 221 onClearAllCache={clearAllCache}
222 isPremiumUser={this.props.stores.user.data.isPremium}
223 isSpellcheckerPremiumFeature={spellcheckerConfig.isPremiumFeature} 222 isSpellcheckerPremiumFeature={spellcheckerConfig.isPremiumFeature}
224 /> 223 />
225 ); 224 );
diff --git a/src/containers/subscription/SubscriptionFormScreen.js b/src/containers/subscription/SubscriptionFormScreen.js
index 9f7571bda..50ed19bef 100644
--- a/src/containers/subscription/SubscriptionFormScreen.js
+++ b/src/containers/subscription/SubscriptionFormScreen.js
@@ -79,7 +79,6 @@ export default @inject('stores', 'actions') @observer class SubscriptionFormScre
79 return ( 79 return (
80 <SubscriptionForm 80 <SubscriptionForm
81 plan={stores.payment.plan} 81 plan={stores.payment.plan}
82 // form={this.prepareForm(stores.payment.plan)}
83 isLoading={stores.payment.plansRequest.isExecuting} 82 isLoading={stores.payment.plansRequest.isExecuting}
84 retryPlanRequest={() => stores.payment.plansRequest.reload()} 83 retryPlanRequest={() => stores.payment.plansRequest.reload()}
85 isCreatingHostedPage={stores.payment.createHostedPageRequest.isExecuting} 84 isCreatingHostedPage={stores.payment.createHostedPageRequest.isExecuting}
diff --git a/src/electron/Settings.js b/src/electron/Settings.js
index 7b04406a2..6ac3b9177 100644
--- a/src/electron/Settings.js
+++ b/src/electron/Settings.js
@@ -1,15 +1,21 @@
1import { observable, toJS } from 'mobx'; 1import { observable, toJS } from 'mobx';
2import { pathExistsSync, outputJsonSync, readJsonSync } from 'fs-extra'; 2import { pathExistsSync, outputJsonSync, readJsonSync } from 'fs-extra';
3import path from 'path';
3 4
4import { SETTINGS_PATH, DEFAULT_APP_SETTINGS } from '../config'; 5import { SETTINGS_PATH } from '../config';
5 6
6const debug = require('debug')('Franz:Settings'); 7const debug = require('debug')('Franz:Settings');
7 8
8export default class Settings { 9export default class Settings {
9 @observable store = DEFAULT_APP_SETTINGS; 10 type = '';
11 @observable store = {};
10 12
11 constructor() { 13 constructor(type, defaultState = {}) {
12 if (!pathExistsSync(SETTINGS_PATH)) { 14 this.type = type;
15 this.store = defaultState;
16 this.defaultState = defaultState;
17
18 if (!pathExistsSync(this.settingsFile)) {
13 this._writeFile(); 19 this._writeFile();
14 } else { 20 } else {
15 this._hydrate(); 21 this._hydrate();
@@ -31,16 +37,20 @@ export default class Settings {
31 } 37 }
32 38
33 _merge(settings) { 39 _merge(settings) {
34 return Object.assign(DEFAULT_APP_SETTINGS, this.store, settings); 40 return Object.assign(this.defaultState, this.store, settings);
35 } 41 }
36 42
37 _hydrate() { 43 _hydrate() {
38 this.store = this._merge(readJsonSync(SETTINGS_PATH)); 44 this.store = this._merge(readJsonSync(this.settingsFile));
39 debug('Hydrate store', toJS(this.store)); 45 debug('Hydrate store', toJS(this.store));
40 } 46 }
41 47
42 _writeFile() { 48 _writeFile() {
43 outputJsonSync(SETTINGS_PATH, this.store); 49 outputJsonSync(this.settingsFile, this.store);
44 debug('Write settings file', toJS(this.store)); 50 debug('Write settings file', toJS(this.store));
45 } 51 }
52
53 get settingsFile() {
54 return path.join(SETTINGS_PATH, `${this.type === 'app' ? 'settings' : this.type}.json`);
55 }
46} 56}
diff --git a/src/electron/ipc-api/appIndicator.js b/src/electron/ipc-api/appIndicator.js
index d31819068..e568bf35d 100644
--- a/src/electron/ipc-api/appIndicator.js
+++ b/src/electron/ipc-api/appIndicator.js
@@ -15,7 +15,7 @@ function getAsset(type, asset) {
15 15
16export default (params) => { 16export default (params) => {
17 autorun(() => { 17 autorun(() => {
18 isTrayIconEnabled = params.settings.get('enableSystemTray'); 18 isTrayIconEnabled = params.settings.app.get('enableSystemTray');
19 19
20 if (!isTrayIconEnabled) { 20 if (!isTrayIconEnabled) {
21 params.trayIcon.hide(); 21 params.trayIcon.hide();
diff --git a/src/electron/ipc-api/settings.js b/src/electron/ipc-api/settings.js
index 3eab68a91..ce006bb92 100644
--- a/src/electron/ipc-api/settings.js
+++ b/src/electron/ipc-api/settings.js
@@ -1,11 +1,15 @@
1import { ipcMain } from 'electron'; 1import { ipcMain } from 'electron';
2 2
3export default (params) => { 3export default (params) => {
4 ipcMain.on('getAppSettings', () => { 4 ipcMain.on('getAppSettings', (event, type) => {
5 params.mainWindow.webContents.send('appSettings', params.settings.all); 5 console.log('getAppSettings', type, params.settings[type].all);
6 params.mainWindow.webContents.send('appSettings', {
7 type,
8 data: params.settings[type].all,
9 });
6 }); 10 });
7 11
8 ipcMain.on('updateAppSettings', (event, args) => { 12 ipcMain.on('updateAppSettings', (event, args) => {
9 params.settings.set(args); 13 params.settings[args.type].set(args.data);
10 }); 14 });
11}; 15};
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js
index 910b54959..334433df8 100644
--- a/src/features/delayApp/index.js
+++ b/src/features/delayApp/index.js
@@ -38,8 +38,8 @@ export default function init(stores) {
38 let shownAfterLaunch = false; 38 let shownAfterLaunch = false;
39 let timeLastDelay = moment(); 39 let timeLastDelay = moment();
40 40
41 config.delayOffset = globalConfig.delayOffset || DEFAULT_DELAY_OFFSET; 41 config.delayOffset = globalConfig.delayOffset !== undefined ? globalConfig.delayOffset : DEFAULT_DELAY_OFFSET;
42 config.delayDuration = globalConfig.wait || DEFAULT_DELAY_DURATION; 42 config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_DELAY_DURATION;
43 43
44 autorun(() => { 44 autorun(() => {
45 const diff = moment().diff(timeLastDelay); 45 const diff = moment().diff(timeLastDelay);
diff --git a/src/features/serviceProxy/index.js b/src/features/serviceProxy/index.js
new file mode 100644
index 000000000..edb1c9367
--- /dev/null
+++ b/src/features/serviceProxy/index.js
@@ -0,0 +1,56 @@
1import { autorun, reaction, observable } from 'mobx';
2import { remote } from 'electron';
3
4const { session } = remote;
5
6const debug = require('debug')('Franz:feature:serviceProxy');
7
8const DEFAULT_ENABLED = false;
9const DEFAULT_IS_PREMIUM = true;
10
11export const config = observable({
12 isEnabled: DEFAULT_ENABLED,
13 isPremium: DEFAULT_IS_PREMIUM,
14});
15
16export default function init(stores) {
17 reaction(
18 () => stores.features.features.isServiceProxyEnabled,
19 (enabled, r) => {
20 if (enabled) {
21 debug('Initializing `serviceProxy` feature');
22
23 // Dispose the reaction to run this only once
24 r.dispose();
25
26 const { isServiceProxyEnabled, isServiceProxyPremiumFeature } = stores.features.features;
27
28 config.isEnabled = isServiceProxyEnabled !== undefined ? isServiceProxyEnabled : DEFAULT_ENABLED;
29 config.isPremium = isServiceProxyPremiumFeature !== undefined ? isServiceProxyPremiumFeature : DEFAULT_IS_PREMIUM;
30
31 autorun(() => {
32 const services = stores.services.all;
33 const isPremiumUser = stores.user.isPremium;
34
35 if (config.isPremium && !isPremiumUser) return;
36
37 services.forEach((service) => {
38 const s = session.fromPartition(`persist:service-${service.id}`);
39 let proxyHost = 'direct://';
40
41 const serviceProxyConfig = stores.settings.proxy[service.id];
42
43 if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) {
44 proxyHost = serviceProxyConfig.host;
45 }
46
47 s.setProxy({ proxyRules: proxyHost }, (e) => {
48 debug(`Using proxy "${proxyHost}" for "${service.name}" (${service.id})`, e);
49 });
50 });
51 });
52 }
53 },
54 );
55}
56
diff --git a/src/features/spellchecker/index.js b/src/features/spellchecker/index.js
index 9336f4074..8b3fb7e00 100644
--- a/src/features/spellchecker/index.js
+++ b/src/features/spellchecker/index.js
@@ -20,10 +20,9 @@ export default function init(stores) {
20 20
21 const { isSpellcheckerPremiumFeature } = stores.features.features; 21 const { isSpellcheckerPremiumFeature } = stores.features.features;
22 22
23 config.isPremiumFeature = isSpellcheckerPremiumFeature || DEFAULT_IS_PREMIUM_FEATURE; 23 config.isPremiumFeature = isSpellcheckerPremiumFeature !== undefined ? isSpellcheckerPremiumFeature : DEFAULT_IS_PREMIUM_FEATURE;
24 24
25 autorun(() => { 25 autorun(() => {
26 console.log('FEATURE spellchecker autorun', stores.user.data.isPremium, config.isPremiumFeature);
27 if (!stores.user.data.isPremium && config.isPremiumFeature) { 26 if (!stores.user.data.isPremium && config.isPremiumFeature) {
28 debug('Override settings.spellcheckerEnabled flag to false'); 27 debug('Override settings.spellcheckerEnabled flag to false');
29 28
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index be2a38231..8d82f98a4 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -133,6 +133,12 @@
133 "settings.service.form.iconDelete": "Delete", 133 "settings.service.form.iconDelete": "Delete",
134 "settings.service.form.iconUpload": "Drop your image, or click here", 134 "settings.service.form.iconUpload": "Drop your image, or click here",
135 "settings.service.form.enableDarkMode": "Enable Dark Mode", 135 "settings.service.form.enableDarkMode": "Enable Dark Mode",
136 "settings.service.form.proxy.headline": "Proxy Settings",
137 "settings.service.form.proxy.isEnabled": "Use Proxy",
138 "settings.service.form.proxy.host": "Proxy Host/IP",
139 "settings.service.form.proxy.user": "User (optional)",
140 "settings.service.form.proxy.password": "Password (optional)",
141 "settings.service.form.proxy.info": "Proxy settings will not synced with the Franz servers.",
136 "settings.service.error.headline": "Error", 142 "settings.service.error.headline": "Error",
137 "settings.service.error.goBack": "Back to services", 143 "settings.service.error.goBack": "Back to services",
138 "settings.service.error.message": "Could not load service recipe.", 144 "settings.service.error.message": "Could not load service recipe.",
diff --git a/src/index.js b/src/index.js
index 7d906ad71..994531dbf 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,4 @@
1import { app, BrowserWindow, shell } from 'electron'; 1import { app, BrowserWindow, shell, ipcMain } from 'electron';
2import fs from 'fs-extra'; 2import fs from 'fs-extra';
3import path from 'path'; 3import path from 'path';
4 4
@@ -12,6 +12,8 @@ import handleDeepLink from './electron/deepLinking';
12import { appId } from './package.json'; // eslint-disable-line import/no-unresolved 12import { appId } from './package.json'; // eslint-disable-line import/no-unresolved
13import './electron/exception'; 13import './electron/exception';
14 14
15import { DEFAULT_APP_SETTINGS } from './config';
16
15const debug = require('debug')('Franz:App'); 17const debug = require('debug')('Franz:App');
16 18
17// Keep a global reference of the window object, if you don't, the window will 19// Keep a global reference of the window object, if you don't, the window will
@@ -62,7 +64,8 @@ if (isLinux && ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESK
62} 64}
63 65
64// Initialize Settings 66// Initialize Settings
65const settings = new Settings(); 67const settings = new Settings('app', DEFAULT_APP_SETTINGS);
68const proxySettings = new Settings('proxy');
66 69
67// Disable GPU acceleration 70// Disable GPU acceleration
68if (!settings.get('enableGPUAcceleration')) { 71if (!settings.get('enableGPUAcceleration')) {
@@ -94,7 +97,14 @@ const createWindow = () => {
94 const trayIcon = new Tray(); 97 const trayIcon = new Tray();
95 98
96 // Initialize ipcApi 99 // Initialize ipcApi
97 ipcApi({ mainWindow, settings, trayIcon }); 100 ipcApi({
101 mainWindow,
102 settings: {
103 app: settings,
104 proxy: proxySettings,
105 },
106 trayIcon,
107 });
98 108
99 // Manage Window State 109 // Manage Window State
100 mainWindowState.manage(mainWindow); 110 mainWindowState.manage(mainWindow);
@@ -177,6 +187,24 @@ const createWindow = () => {
177// Some APIs can only be used after this event occurs. 187// Some APIs can only be used after this event occurs.
178app.on('ready', createWindow); 188app.on('ready', createWindow);
179 189
190// This is the worst possible implementation as the webview.webContents based callback doesn't work 🖕
191app.on('login', (event, webContents, request, authInfo, callback) => {
192 event.preventDefault();
193 debug('browser login event', authInfo);
194 if (authInfo.isProxy && authInfo.scheme === 'basic') {
195 webContents.send('get-service-id');
196
197 ipcMain.on('service-id', (e, id) => {
198 debug('Received service id', id);
199
200 const ps = proxySettings.get(id);
201 callback(ps.user, ps.password);
202 });
203 } else {
204 // TODO: implement basic auth
205 }
206});
207
180// Quit when all windows are closed. 208// Quit when all windows are closed.
181app.on('window-all-closed', () => { 209app.on('window-all-closed', () => {
182 // On OS X it is common for applications and their menu bar 210 // On OS X it is common for applications and their menu bar
diff --git a/src/models/Service.js b/src/models/Service.js
index d04b34b7e..41180dd76 100644
--- a/src/models/Service.js
+++ b/src/models/Service.js
@@ -30,10 +30,6 @@ export default class Service {
30 @observable hasCrashed = false; 30 @observable hasCrashed = false;
31 @observable isDarkModeEnabled = false; 31 @observable isDarkModeEnabled = false;
32 32
33 // @observable proxy = {
34 // isEnabled: false,
35 // };
36
37 constructor(data, recipe) { 33 constructor(data, recipe) {
38 if (!data) { 34 if (!data) {
39 console.error('Service config not valid'); 35 console.error('Service config not valid');
@@ -73,6 +69,8 @@ export default class Service {
73 69
74 this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon; 70 this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon;
75 71
72 this.proxy = data.proxy !== undefined ? data.proxy : this.proxy;
73
76 this.recipe = recipe; 74 this.recipe = recipe;
77 75
78 autorun(() => { 76 autorun(() => {
diff --git a/src/models/Settings.js b/src/models/Settings.js
index 0e4c59057..87ab8de67 100644
--- a/src/models/Settings.js
+++ b/src/models/Settings.js
@@ -4,25 +4,22 @@ import { DEFAULT_APP_SETTINGS } from '../config';
4export default class Settings { 4export default class Settings {
5 @observable app = DEFAULT_APP_SETTINGS 5 @observable app = DEFAULT_APP_SETTINGS
6 6
7 @observable proxy = {}
8
7 @observable service = { 9 @observable service = {
8 activeService: '', 10 activeService: '',
9 } 11 }
10 12
11 @observable group = {
12 collapsed: [],
13 disabled: [],
14 }
15
16 @observable stats = { 13 @observable stats = {
17 appStarts: 0, 14 appStarts: 0,
18 } 15 }
19 16
20 @observable migration = {} 17 @observable migration = {}
21 18
22 constructor({ app, service, group, stats, migration }) { 19 constructor({ app, proxy, service, stats, migration }) {
23 Object.assign(this.app, app); 20 Object.assign(this.app, app);
21 Object.assign(this.proxy, proxy);
24 Object.assign(this.service, service); 22 Object.assign(this.service, service);
25 Object.assign(this.group, group);
26 Object.assign(this.stats, stats); 23 Object.assign(this.stats, stats);
27 Object.assign(this.migration, migration); 24 Object.assign(this.migration, migration);
28 } 25 }
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index dd4827221..59abeb218 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -5,6 +5,7 @@ import CachedRequest from './lib/CachedRequest';
5 5
6import delayApp from '../features/delayApp'; 6import delayApp from '../features/delayApp';
7import spellchecker from '../features/spellchecker'; 7import spellchecker from '../features/spellchecker';
8import serviceProxy from '../features/serviceProxy';
8 9
9export default class FeaturesStore extends Store { 10export default class FeaturesStore extends Store {
10 @observable defaultFeaturesRequest = new CachedRequest(this.api.features, 'default'); 11 @observable defaultFeaturesRequest = new CachedRequest(this.api.features, 'default');
@@ -38,5 +39,6 @@ export default class FeaturesStore extends Store {
38 _enableFeatures() { 39 _enableFeatures() {
39 delayApp(this.stores, this.actions); 40 delayApp(this.stores, this.actions);
40 spellchecker(this.stores, this.actions); 41 spellchecker(this.stores, this.actions);
42 serviceProxy(this.stores, this.actions);
41 } 43 }
42} 44}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index cdb2db142..e22b343e7 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -143,6 +143,7 @@ export default class ServicesStore extends Store {
143 // Actions 143 // Actions
144 @action async _createService({ recipeId, serviceData, redirect = true }) { 144 @action async _createService({ recipeId, serviceData, redirect = true }) {
145 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); 145 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
146
146 const response = await this.createServiceRequest.execute(recipeId, data)._promise; 147 const response = await this.createServiceRequest.execute(recipeId, data)._promise;
147 148
148 this.allServicesRequest.patch((result) => { 149 this.allServicesRequest.patch((result) => {
@@ -150,6 +151,13 @@ export default class ServicesStore extends Store {
150 result.push(response.data); 151 result.push(response.data);
151 }); 152 });
152 153
154 this.actions.settings.update({
155 type: 'proxy',
156 data: {
157 [`${response.data.id}`]: data.proxy,
158 },
159 });
160
153 this.actionStatus = response.status || []; 161 this.actionStatus = response.status || [];
154 162
155 if (redirect) { 163 if (redirect) {
@@ -222,6 +230,13 @@ export default class ServicesStore extends Store {
222 }); 230 });
223 } 231 }
224 232
233 this.actions.settings.update({
234 type: 'proxy',
235 data: {
236 [`${serviceId}`]: data.proxy,
237 },
238 });
239
225 if (redirect) { 240 if (redirect) {
226 this.stores.router.push('/settings/services'); 241 this.stores.router.push('/settings/services');
227 gaEvent('Service', 'update', service.recipe.id); 242 gaEvent('Service', 'update', service.recipe.id);
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
index f1b067115..a5c2c8b52 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.js
@@ -1,4 +1,4 @@
1import { remote } from 'electron'; 1import { remote, ipcRenderer } from 'electron';
2import { action, computed, observable } from 'mobx'; 2import { action, computed, observable } from 'mobx';
3import localStorage from 'mobx-localstorage'; 3import localStorage from 'mobx-localstorage';
4 4
@@ -7,6 +7,8 @@ import SettingsModel from '../models/Settings';
7import Request from './lib/Request'; 7import Request from './lib/Request';
8import CachedRequest from './lib/CachedRequest'; 8import CachedRequest from './lib/CachedRequest';
9 9
10import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES } from '../config';
11
10const { systemPreferences } = remote; 12const { systemPreferences } = remote;
11const debug = require('debug')('Franz:SettingsStore'); 13const debug = require('debug')('Franz:SettingsStore');
12 14
@@ -14,12 +16,35 @@ export default class SettingsStore extends Store {
14 @observable appSettingsRequest = new CachedRequest(this.api.local, 'getAppSettings'); 16 @observable appSettingsRequest = new CachedRequest(this.api.local, 'getAppSettings');
15 @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings'); 17 @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings');
16 18
19 @observable fileSystemSettingsRequests = [];
20
21 fileSystemSettingsTypes = FILE_SYSTEM_SETTINGS_TYPES;
22 @observable _fileSystemSettingsCache = {
23 app: DEFAULT_APP_SETTINGS,
24 proxy: {},
25 };
26
17 constructor(...args) { 27 constructor(...args) {
18 super(...args); 28 super(...args);
19 29
20 // Register action handlers 30 // Register action handlers
21 this.actions.settings.update.listen(this._update.bind(this)); 31 this.actions.settings.update.listen(this._update.bind(this));
22 this.actions.settings.remove.listen(this._remove.bind(this)); 32 this.actions.settings.remove.listen(this._remove.bind(this));
33
34 this.fileSystemSettingsTypes.forEach((type) => {
35 this.fileSystemSettingsRequests[type] = new CachedRequest(this.api.local, 'getAppSettings');
36 });
37
38 ipcRenderer.on('appSettings', (event, resp) => {
39 debug('Get appSettings resolves', resp, resp.type, resp.data);
40
41 this._fileSystemSettingsCache[resp.type] = resp.data;
42 });
43
44 this.fileSystemSettingsTypes.forEach((type) => {
45 console.log(type);
46 ipcRenderer.send('getAppSettings', type);
47 });
23 } 48 }
24 49
25 async setup() { 50 async setup() {
@@ -28,29 +53,53 @@ export default class SettingsStore extends Store {
28 await this._migrate(); 53 await this._migrate();
29 } 54 }
30 55
56 @computed get app() {
57 return this._fileSystemSettingsCache.app || DEFAULT_APP_SETTINGS;
58 }
59
60 @computed get proxy() {
61 return this._fileSystemSettingsCache.proxy || {};
62 }
63
64 @computed get service() {
65 return localStorage.getItem('service') || {
66 activeService: '',
67 };
68 }
69
70 @computed get stats() {
71 return localStorage.getItem('stats') || {
72 activeService: '',
73 };
74 }
75
76 @computed get migration() {
77 return localStorage.getItem('migration') || {};
78 }
79
31 @computed get all() { 80 @computed get all() {
32 return new SettingsModel({ 81 return {
33 app: this.appSettingsRequest.execute().result || {}, 82 app: this.app,
34 service: localStorage.getItem('service') || {}, 83 proxy: this.proxy,
35 group: localStorage.getItem('group') || {}, 84 service: this.service,
36 stats: localStorage.getItem('stats') || {}, 85 stats: this.stats,
37 migration: localStorage.getItem('migration') || {}, 86 migration: this.migration,
38 }); 87 };
39 } 88 }
40 89
41 @action async _update({ type, data }) { 90 @action async _update({ type, data }) {
42 const appSettings = this.all; 91 const appSettings = this.all;
43 if (type !== 'app') { 92 if (!this.fileSystemSettingsTypes.includes(type)) {
44 debug('Update settings', type, data, this.all); 93 debug('Update settings', type, data, this.all);
45 localStorage.setItem(type, Object.assign(appSettings[type], data)); 94 localStorage.setItem(type, Object.assign(appSettings[type], data));
46 } else { 95 } else {
47 debug('Update settings on file system', type, data); 96 debug('Update settings on file system', type, data);
48 this.updateAppSettingsRequest.execute(data); 97 ipcRenderer.send('updateAppSettings', {
49 98 type,
50 this.appSettingsRequest.patch((result) => { 99 data,
51 if (!result) return;
52 Object.assign(result, data);
53 }); 100 });
101
102 Object.assign(this._fileSystemSettingsCache[type], data);
54 } 103 }
55 } 104 }
56 105
@@ -128,4 +177,8 @@ export default class SettingsStore extends Store {
128 debug('Set up dark mode'); 177 debug('Set up dark mode');
129 } 178 }
130 } 179 }
180
181 _getFileBasedSettings(type) {
182 ipcRenderer.send('getAppSettings', type);
183 }
131} 184}
diff --git a/src/styles/settings.scss b/src/styles/settings.scss
index 5e7e35fd8..f94ca114d 100644
--- a/src/styles/settings.scss
+++ b/src/styles/settings.scss
@@ -249,6 +249,11 @@
249 margin: 25px 0 15px; 249 margin: 25px 0 15px;
250 250
251 &:first-of-type { margin-top: 0; } 251 &:first-of-type { margin-top: 0; }
252
253 .badge {
254 font-weight: normal;
255 margin-left: 10px;
256 }
252 } 257 }
253 } 258 }
254 259
diff --git a/src/webview/plugin.js b/src/webview/plugin.js
index e6fdc4efd..427ec75ad 100644
--- a/src/webview/plugin.js
+++ b/src/webview/plugin.js
@@ -64,7 +64,13 @@ ipcRenderer.on('service-settings-update', (e, data) => {
64 } 64 }
65}); 65});
66 66
67// initSpellchecker 67// Needed for current implementation of electrons 'login' event
68ipcRenderer.on('get-service-id', (event) => {
69 debug('Asking for service id', event);
70
71 event.sender.send('service-id', serviceData.id);
72});
73
68 74
69document.addEventListener('DOMContentLoaded', () => { 75document.addEventListener('DOMContentLoaded', () => {
70 ipcRenderer.sendToHost('hello'); 76 ipcRenderer.sendToHost('hello');