diff options
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 | ||
10 | export default class LocalApi { | 10 | export 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 | ||
100 | SettingsNavigation.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'; | |||
15 | import Button from '../../ui/Button'; | 15 | import Button from '../../ui/Button'; |
16 | import ImageUpload from '../../ui/ImageUpload'; | 16 | import ImageUpload from '../../ui/ImageUpload'; |
17 | 17 | ||
18 | import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; | ||
19 | |||
18 | const messages = defineMessages({ | 20 | const 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 | ||
97 | export default @observer class EditServiceForm extends Component { | 107 | export 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 | ||
7 | import { oneOrManyChildElements } from '../../../prop-types'; | 7 | import { oneOrManyChildElements } from '../../../prop-types'; |
8 | 8 | ||
9 | import UserStore from '../../../stores/UserStore'; | ||
10 | |||
9 | import styles from './styles'; | 11 | import styles from './styles'; |
10 | 12 | ||
11 | const messages = defineMessages({ | 13 | const messages = defineMessages({ |
@@ -15,9 +17,14 @@ const messages = defineMessages({ | |||
15 | }, | 17 | }, |
16 | }); | 18 | }); |
17 | 19 | ||
18 | export default @inject('actions') @injectSheet(styles) @observer class PremiumFeatureContainer extends Component { | 20 | export 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 | ||
56 | PremiumFeatureContainer.wrappedComponent.propTypes = { | 65 | PremiumFeatureContainer.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 = { | |||
29 | export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-service-request'; | 29 | export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-service-request'; |
30 | export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate'; | 30 | export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate'; |
31 | 31 | ||
32 | export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config', 'settings.json'); | 32 | export const FILE_SYSTEM_SETTINGS_TYPES = [ |
33 | 'app', | ||
34 | 'proxy', | ||
35 | ]; | ||
36 | |||
37 | export 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'; | |||
6 | import UserStore from '../../stores/UserStore'; | 6 | import UserStore from '../../stores/UserStore'; |
7 | import RecipesStore from '../../stores/RecipesStore'; | 7 | import RecipesStore from '../../stores/RecipesStore'; |
8 | import ServicesStore from '../../stores/ServicesStore'; | 8 | import ServicesStore from '../../stores/ServicesStore'; |
9 | import FeaturesStore from '../../stores/FeaturesStore'; | ||
10 | import SettingsStore from '../../stores/SettingsStore'; | 9 | import SettingsStore from '../../stores/SettingsStore'; |
10 | import FeaturesStore from '../../stores/FeaturesStore'; | ||
11 | import Form from '../../lib/Form'; | 11 | import Form from '../../lib/Form'; |
12 | import { gaPage } from '../../lib/analytics'; | 12 | import { gaPage } from '../../lib/analytics'; |
13 | 13 | ||
@@ -15,6 +15,8 @@ import ServiceError from '../../components/settings/services/ServiceError'; | |||
15 | import EditServiceForm from '../../components/settings/services/EditServiceForm'; | 15 | import EditServiceForm from '../../components/settings/services/EditServiceForm'; |
16 | import { required, url, oneRequired } from '../../helpers/validation-helpers'; | 16 | import { required, url, oneRequired } from '../../helpers/validation-helpers'; |
17 | 17 | ||
18 | import { config as proxyFeature } from '../../features/serviceProxy'; | ||
19 | |||
18 | const messages = defineMessages({ | 20 | const 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 | ||
61 | export default @inject('stores', 'actions') @observer class EditServiceScreen extends Component { | 79 | export 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 @@ | |||
1 | import { observable, toJS } from 'mobx'; | 1 | import { observable, toJS } from 'mobx'; |
2 | import { pathExistsSync, outputJsonSync, readJsonSync } from 'fs-extra'; | 2 | import { pathExistsSync, outputJsonSync, readJsonSync } from 'fs-extra'; |
3 | import path from 'path'; | ||
3 | 4 | ||
4 | import { SETTINGS_PATH, DEFAULT_APP_SETTINGS } from '../config'; | 5 | import { SETTINGS_PATH } from '../config'; |
5 | 6 | ||
6 | const debug = require('debug')('Franz:Settings'); | 7 | const debug = require('debug')('Franz:Settings'); |
7 | 8 | ||
8 | export default class Settings { | 9 | export 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 | ||
16 | export default (params) => { | 16 | export 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 @@ | |||
1 | import { ipcMain } from 'electron'; | 1 | import { ipcMain } from 'electron'; |
2 | 2 | ||
3 | export default (params) => { | 3 | export 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 @@ | |||
1 | import { autorun, reaction, observable } from 'mobx'; | ||
2 | import { remote } from 'electron'; | ||
3 | |||
4 | const { session } = remote; | ||
5 | |||
6 | const debug = require('debug')('Franz:feature:serviceProxy'); | ||
7 | |||
8 | const DEFAULT_ENABLED = false; | ||
9 | const DEFAULT_IS_PREMIUM = true; | ||
10 | |||
11 | export const config = observable({ | ||
12 | isEnabled: DEFAULT_ENABLED, | ||
13 | isPremium: DEFAULT_IS_PREMIUM, | ||
14 | }); | ||
15 | |||
16 | export 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 @@ | |||
1 | import { app, BrowserWindow, shell } from 'electron'; | 1 | import { app, BrowserWindow, shell, ipcMain } from 'electron'; |
2 | import fs from 'fs-extra'; | 2 | import fs from 'fs-extra'; |
3 | import path from 'path'; | 3 | import path from 'path'; |
4 | 4 | ||
@@ -12,6 +12,8 @@ import handleDeepLink from './electron/deepLinking'; | |||
12 | import { appId } from './package.json'; // eslint-disable-line import/no-unresolved | 12 | import { appId } from './package.json'; // eslint-disable-line import/no-unresolved |
13 | import './electron/exception'; | 13 | import './electron/exception'; |
14 | 14 | ||
15 | import { DEFAULT_APP_SETTINGS } from './config'; | ||
16 | |||
15 | const debug = require('debug')('Franz:App'); | 17 | const 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 |
65 | const settings = new Settings(); | 67 | const settings = new Settings('app', DEFAULT_APP_SETTINGS); |
68 | const proxySettings = new Settings('proxy'); | ||
66 | 69 | ||
67 | // Disable GPU acceleration | 70 | // Disable GPU acceleration |
68 | if (!settings.get('enableGPUAcceleration')) { | 71 | if (!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. |
178 | app.on('ready', createWindow); | 188 | app.on('ready', createWindow); |
179 | 189 | ||
190 | // This is the worst possible implementation as the webview.webContents based callback doesn't work 🖕 | ||
191 | app.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. |
181 | app.on('window-all-closed', () => { | 209 | app.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'; | |||
4 | export default class Settings { | 4 | export 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 | ||
6 | import delayApp from '../features/delayApp'; | 6 | import delayApp from '../features/delayApp'; |
7 | import spellchecker from '../features/spellchecker'; | 7 | import spellchecker from '../features/spellchecker'; |
8 | import serviceProxy from '../features/serviceProxy'; | ||
8 | 9 | ||
9 | export default class FeaturesStore extends Store { | 10 | export 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 @@ | |||
1 | import { remote } from 'electron'; | 1 | import { remote, ipcRenderer } from 'electron'; |
2 | import { action, computed, observable } from 'mobx'; | 2 | import { action, computed, observable } from 'mobx'; |
3 | import localStorage from 'mobx-localstorage'; | 3 | import localStorage from 'mobx-localstorage'; |
4 | 4 | ||
@@ -7,6 +7,8 @@ import SettingsModel from '../models/Settings'; | |||
7 | import Request from './lib/Request'; | 7 | import Request from './lib/Request'; |
8 | import CachedRequest from './lib/CachedRequest'; | 8 | import CachedRequest from './lib/CachedRequest'; |
9 | 9 | ||
10 | import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES } from '../config'; | ||
11 | |||
10 | const { systemPreferences } = remote; | 12 | const { systemPreferences } = remote; |
11 | const debug = require('debug')('Franz:SettingsStore'); | 13 | const 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 |
68 | ipcRenderer.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 | ||
69 | document.addEventListener('DOMContentLoaded', () => { | 75 | document.addEventListener('DOMContentLoaded', () => { |
70 | ipcRenderer.sendToHost('hello'); | 76 | ipcRenderer.sendToHost('hello'); |