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 {
this.local = local;
}
- getAppSettings() {
- return this.local.getAppSettings();
+ getAppSettings(type) {
+ return this.local.getAppSettings(type);
}
- updateAppSettings(data) {
- return this.local.updateAppSettings(data);
+ updateAppSettings(type, data) {
+ return this.local.updateAppSettings(type, data);
}
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;
export default class LocalApi {
// Settings
- getAppSettings() {
+ getAppSettings(type) {
return new Promise((resolve) => {
- ipcRenderer.once('appSettings', (event, data) => {
- debug('LocalApi::getAppSettings resolves', data);
- resolve(data);
+ ipcRenderer.once('appSettings', (event, resp) => {
+ debug('LocalApi::getAppSettings resolves', resp.type, resp.data);
+ resolve(resp);
});
- ipcRenderer.send('getAppSettings');
+ ipcRenderer.send('getAppSettings', type);
});
}
- async updateAppSettings(data) {
- debug('LocalApi::updateAppSettings resolves', data);
- ipcRenderer.send('updateAppSettings', data);
+ async updateAppSettings(type, data) {
+ debug('LocalApi::updateAppSettings resolves', type, data);
+ ipcRenderer.send('updateAppSettings', {
+ type,
+ data,
+ });
}
// 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 {
{intl.formatMessage(messages.accountTypeEnterprise)}
)}
- {!user.isSSO && (
-
- {intl.formatMessage(messages.accountEditButton)}
-
- )}
+
+ {intl.formatMessage(messages.accountEditButton)}
+
{user.emailValidated}
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
render() {
const { serviceCount } = this.props;
- const { features } = this.props.stores.features;
const { intl } = this.context;
return (
{/* Delete Button */}
- {action === 'edit' && userCanManageServices && deleteButton}
+ {action === 'edit' && deleteButton}
{/* Save Button */}
{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 {
isClearingAllCache: PropTypes.bool.isRequired,
onClearAllCache: PropTypes.func.isRequired,
cacheSize: PropTypes.string.isRequired,
- isPremiumUser: PropTypes.bool.isRequired,
isSpellcheckerPremiumFeature: PropTypes.bool.isRequired,
};
@@ -127,7 +126,6 @@ export default @observer class EditSettingsForm extends Component {
isClearingAllCache,
onClearAllCache,
cacheSize,
- isPremiumUser,
isSpellcheckerPremiumFeature,
} = this.props;
const { intl } = this.context;
@@ -180,16 +178,14 @@ export default @observer class EditSettingsForm extends Component {
{/* Advanced */}
{intl.formatMessage(messages.headlineAdvanced)}
- {!isPremiumUser && isSpellcheckerPremiumFeature ? (
-
-
-
- ) : (
-
- )}
+
+
+
{intl.formatMessage(messages.enableGPUAccelerationInfo)}
{/*
*/}
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({
id: 'subscription.features.onpremise.mattermost',
defaultMessage: '!!!Add on-premise/hosted services like Mattermost',
},
- encryptedSync: {
- id: 'subscription.features.encryptedSync',
- defaultMessage: '!!!Encrypted session synchronization',
noInterruptions: {
id: 'subscription.features.noInterruptions',
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';
import { oneOrManyChildElements } from '../../../prop-types';
+import UserStore from '../../../stores/UserStore';
+
import styles from './styles';
const messages = defineMessages({
@@ -15,9 +17,14 @@ const messages = defineMessages({
},
});
-export default @inject('actions') @injectSheet(styles) @observer class PremiumFeatureContainer extends Component {
+export default @inject('stores', 'actions') @injectSheet(styles) @observer class PremiumFeatureContainer extends Component {
static propTypes = {
classes: PropTypes.object.isRequired,
+ condition: PropTypes.bool,
+ };
+
+ static defaultProps = {
+ condition: true,
};
static contextTypes = {
@@ -29,11 +36,13 @@ export default @inject('actions') @injectSheet(styles) @observer class PremiumFe
classes,
children,
actions,
+ condition,
+ stores,
} = this.props;
const { intl } = this.context;
- return (
+ return !stores.user.data.isPremium && !!condition ? (
Premium Feature
@@ -49,12 +58,15 @@ export default @inject('actions') @injectSheet(styles) @observer class PremiumFe
{children}
- );
+ ) : children;
}
}
PremiumFeatureContainer.wrappedComponent.propTypes = {
children: oneOrManyChildElements.isRequired,
+ stores: PropTypes.shape({
+ user: PropTypes.instanceOf(UserStore).isRequired,
+ }).isRequired,
actions: PropTypes.shape({
ui: PropTypes.shape({
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 = {
export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-service-request';
export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate';
-export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config', 'settings.json');
+export const FILE_SYSTEM_SETTINGS_TYPES = [
+ 'app',
+ 'proxy',
+];
+
+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';
import UserStore from '../../stores/UserStore';
import RecipesStore from '../../stores/RecipesStore';
import ServicesStore from '../../stores/ServicesStore';
-import FeaturesStore from '../../stores/FeaturesStore';
import SettingsStore from '../../stores/SettingsStore';
+import FeaturesStore from '../../stores/FeaturesStore';
import Form from '../../lib/Form';
import { gaPage } from '../../lib/analytics';
@@ -15,6 +15,8 @@ import ServiceError from '../../components/settings/services/ServiceError';
import EditServiceForm from '../../components/settings/services/EditServiceForm';
import { required, url, oneRequired } from '../../helpers/validation-helpers';
+import { config as proxyFeature } from '../../features/serviceProxy';
+
const messages = defineMessages({
name: {
id: 'settings.service.form.name',
@@ -56,6 +58,22 @@ const messages = defineMessages({
id: 'settings.service.form.enableDarkMode',
defaultMessage: '!!!Enable Dark Mode',
},
+ enableProxy: {
+ id: 'settings.service.form.proxy.isEnabled',
+ defaultMessage: '!!!Use Proxy',
+ },
+ proxyHost: {
+ id: 'settings.service.form.proxy.host',
+ defaultMessage: '!!!Proxy Host/IP',
+ },
+ proxyUser: {
+ id: 'settings.service.form.proxy.user',
+ defaultMessage: '!!!User',
+ },
+ proxyPassword: {
+ id: 'settings.service.form.proxy.password',
+ defaultMessage: '!!!Password',
+ },
});
export default @inject('stores', 'actions') @observer class EditServiceScreen extends Component {
@@ -82,7 +100,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
}
}
- prepareForm(recipe, service, userCanManageServices) {
+ prepareForm(recipe, service, proxy) {
const { intl } = this.context;
const config = {
fields: {
@@ -128,7 +146,6 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
if (recipe.hasTeamId) {
Object.assign(config.fields, {
team: {
- disabled: !userCanManageServices,
label: intl.formatMessage(messages.team),
placeholder: intl.formatMessage(messages.team),
value: service.team,
@@ -140,7 +157,6 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
if (recipe.hasCustomUrl) {
Object.assign(config.fields, {
customUrl: {
- disabled: !userCanManageServices,
label: intl.formatMessage(messages.customUrl),
placeholder: 'https://',
value: service.customUrl,
@@ -175,6 +191,40 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
});
}
+ if (proxy.isEnabled) {
+ const serviceProxyConfig = this.props.stores.settings.proxy[service.id] || {};
+
+ Object.assign(config.fields, {
+ proxy: {
+ name: 'proxy',
+ label: 'proxy',
+ fields: {
+ isEnabled: {
+ label: intl.formatMessage(messages.enableProxy),
+ value: serviceProxyConfig.isEnabled,
+ default: false,
+ },
+ host: {
+ label: intl.formatMessage(messages.proxyHost),
+ value: serviceProxyConfig.host,
+ default: '',
+ },
+ user: {
+ label: intl.formatMessage(messages.proxyUser),
+ value: serviceProxyConfig.user,
+ default: '',
+ },
+ password: {
+ label: intl.formatMessage(messages.proxyPassword),
+ value: serviceProxyConfig.password,
+ default: '',
+ type: 'password',
+ },
+ },
+ },
+ });
+ }
+
return new Form(config);
}
@@ -192,7 +242,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
}
render() {
- const { recipes, services, user, features } = this.props.stores;
+ const { recipes, services, user } = this.props.stores;
const { action } = this.props.router.params;
let recipe;
@@ -227,8 +277,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
);
}
- const userCanManageServices = features.features.userCanManageServices;
- const form = this.prepareForm(recipe, service, userCanManageServices);
+ const form = this.prepareForm(recipe, service, proxyFeature);
return (
this.onSubmit(d)}
onDelete={() => this.deleteService()}
+ isProxyFeatureEnabled={proxyFeature.isEnabled}
+ isProxyFeaturePremiumFeature={proxyFeature.isPremium}
/>
);
}
@@ -253,8 +303,8 @@ EditServiceScreen.wrappedComponent.propTypes = {
user: PropTypes.instanceOf(UserStore).isRequired,
recipes: PropTypes.instanceOf(RecipesStore).isRequired,
services: PropTypes.instanceOf(ServicesStore).isRequired,
- features: PropTypes.instanceOf(FeaturesStore).isRequired,
settings: PropTypes.instanceOf(SettingsStore).isRequired,
+ features: PropTypes.instanceOf(FeaturesStore).isRequired,
}).isRequired,
router: PropTypes.shape({
params: PropTypes.shape({
@@ -267,5 +317,8 @@ EditServiceScreen.wrappedComponent.propTypes = {
updateService: PropTypes.func.isRequired,
deleteService: PropTypes.func.isRequired,
}).isRequired,
+ // settings: PropTypes.shape({
+ // update: PropTypes.func.isRequred,
+ // }).isRequired,
}).isRequired,
};
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
cacheSize={cacheSize}
isClearingAllCache={isClearingAllCache}
onClearAllCache={clearAllCache}
- isPremiumUser={this.props.stores.user.data.isPremium}
isSpellcheckerPremiumFeature={spellcheckerConfig.isPremiumFeature}
/>
);
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
return (
stores.payment.plansRequest.reload()}
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 @@
import { observable, toJS } from 'mobx';
import { pathExistsSync, outputJsonSync, readJsonSync } from 'fs-extra';
+import path from 'path';
-import { SETTINGS_PATH, DEFAULT_APP_SETTINGS } from '../config';
+import { SETTINGS_PATH } from '../config';
const debug = require('debug')('Franz:Settings');
export default class Settings {
- @observable store = DEFAULT_APP_SETTINGS;
+ type = '';
+ @observable store = {};
- constructor() {
- if (!pathExistsSync(SETTINGS_PATH)) {
+ constructor(type, defaultState = {}) {
+ this.type = type;
+ this.store = defaultState;
+ this.defaultState = defaultState;
+
+ if (!pathExistsSync(this.settingsFile)) {
this._writeFile();
} else {
this._hydrate();
@@ -31,16 +37,20 @@ export default class Settings {
}
_merge(settings) {
- return Object.assign(DEFAULT_APP_SETTINGS, this.store, settings);
+ return Object.assign(this.defaultState, this.store, settings);
}
_hydrate() {
- this.store = this._merge(readJsonSync(SETTINGS_PATH));
+ this.store = this._merge(readJsonSync(this.settingsFile));
debug('Hydrate store', toJS(this.store));
}
_writeFile() {
- outputJsonSync(SETTINGS_PATH, this.store);
+ outputJsonSync(this.settingsFile, this.store);
debug('Write settings file', toJS(this.store));
}
+
+ get settingsFile() {
+ return path.join(SETTINGS_PATH, `${this.type === 'app' ? 'settings' : this.type}.json`);
+ }
}
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) {
export default (params) => {
autorun(() => {
- isTrayIconEnabled = params.settings.get('enableSystemTray');
+ isTrayIconEnabled = params.settings.app.get('enableSystemTray');
if (!isTrayIconEnabled) {
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 @@
import { ipcMain } from 'electron';
export default (params) => {
- ipcMain.on('getAppSettings', () => {
- params.mainWindow.webContents.send('appSettings', params.settings.all);
+ ipcMain.on('getAppSettings', (event, type) => {
+ console.log('getAppSettings', type, params.settings[type].all);
+ params.mainWindow.webContents.send('appSettings', {
+ type,
+ data: params.settings[type].all,
+ });
});
ipcMain.on('updateAppSettings', (event, args) => {
- params.settings.set(args);
+ params.settings[args.type].set(args.data);
});
};
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) {
let shownAfterLaunch = false;
let timeLastDelay = moment();
- config.delayOffset = globalConfig.delayOffset || DEFAULT_DELAY_OFFSET;
- config.delayDuration = globalConfig.wait || DEFAULT_DELAY_DURATION;
+ config.delayOffset = globalConfig.delayOffset !== undefined ? globalConfig.delayOffset : DEFAULT_DELAY_OFFSET;
+ config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_DELAY_DURATION;
autorun(() => {
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 @@
+import { autorun, reaction, observable } from 'mobx';
+import { remote } from 'electron';
+
+const { session } = remote;
+
+const debug = require('debug')('Franz:feature:serviceProxy');
+
+const DEFAULT_ENABLED = false;
+const DEFAULT_IS_PREMIUM = true;
+
+export const config = observable({
+ isEnabled: DEFAULT_ENABLED,
+ isPremium: DEFAULT_IS_PREMIUM,
+});
+
+export default function init(stores) {
+ reaction(
+ () => stores.features.features.isServiceProxyEnabled,
+ (enabled, r) => {
+ if (enabled) {
+ debug('Initializing `serviceProxy` feature');
+
+ // Dispose the reaction to run this only once
+ r.dispose();
+
+ const { isServiceProxyEnabled, isServiceProxyPremiumFeature } = stores.features.features;
+
+ config.isEnabled = isServiceProxyEnabled !== undefined ? isServiceProxyEnabled : DEFAULT_ENABLED;
+ config.isPremium = isServiceProxyPremiumFeature !== undefined ? isServiceProxyPremiumFeature : DEFAULT_IS_PREMIUM;
+
+ autorun(() => {
+ const services = stores.services.all;
+ const isPremiumUser = stores.user.isPremium;
+
+ if (config.isPremium && !isPremiumUser) return;
+
+ services.forEach((service) => {
+ const s = session.fromPartition(`persist:service-${service.id}`);
+ let proxyHost = 'direct://';
+
+ const serviceProxyConfig = stores.settings.proxy[service.id];
+
+ if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) {
+ proxyHost = serviceProxyConfig.host;
+ }
+
+ s.setProxy({ proxyRules: proxyHost }, (e) => {
+ debug(`Using proxy "${proxyHost}" for "${service.name}" (${service.id})`, e);
+ });
+ });
+ });
+ }
+ },
+ );
+}
+
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) {
const { isSpellcheckerPremiumFeature } = stores.features.features;
- config.isPremiumFeature = isSpellcheckerPremiumFeature || DEFAULT_IS_PREMIUM_FEATURE;
+ config.isPremiumFeature = isSpellcheckerPremiumFeature !== undefined ? isSpellcheckerPremiumFeature : DEFAULT_IS_PREMIUM_FEATURE;
autorun(() => {
- console.log('FEATURE spellchecker autorun', stores.user.data.isPremium, config.isPremiumFeature);
if (!stores.user.data.isPremium && config.isPremiumFeature) {
debug('Override settings.spellcheckerEnabled flag to false');
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 @@
"settings.service.form.iconDelete": "Delete",
"settings.service.form.iconUpload": "Drop your image, or click here",
"settings.service.form.enableDarkMode": "Enable Dark Mode",
+ "settings.service.form.proxy.headline": "Proxy Settings",
+ "settings.service.form.proxy.isEnabled": "Use Proxy",
+ "settings.service.form.proxy.host": "Proxy Host/IP",
+ "settings.service.form.proxy.user": "User (optional)",
+ "settings.service.form.proxy.password": "Password (optional)",
+ "settings.service.form.proxy.info": "Proxy settings will not synced with the Franz servers.",
"settings.service.error.headline": "Error",
"settings.service.error.goBack": "Back to services",
"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 @@
-import { app, BrowserWindow, shell } from 'electron';
+import { app, BrowserWindow, shell, ipcMain } from 'electron';
import fs from 'fs-extra';
import path from 'path';
@@ -12,6 +12,8 @@ import handleDeepLink from './electron/deepLinking';
import { appId } from './package.json'; // eslint-disable-line import/no-unresolved
import './electron/exception';
+import { DEFAULT_APP_SETTINGS } from './config';
+
const debug = require('debug')('Franz:App');
// 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
}
// Initialize Settings
-const settings = new Settings();
+const settings = new Settings('app', DEFAULT_APP_SETTINGS);
+const proxySettings = new Settings('proxy');
// Disable GPU acceleration
if (!settings.get('enableGPUAcceleration')) {
@@ -94,7 +97,14 @@ const createWindow = () => {
const trayIcon = new Tray();
// Initialize ipcApi
- ipcApi({ mainWindow, settings, trayIcon });
+ ipcApi({
+ mainWindow,
+ settings: {
+ app: settings,
+ proxy: proxySettings,
+ },
+ trayIcon,
+ });
// Manage Window State
mainWindowState.manage(mainWindow);
@@ -177,6 +187,24 @@ const createWindow = () => {
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
+// This is the worst possible implementation as the webview.webContents based callback doesn't work 🖕
+app.on('login', (event, webContents, request, authInfo, callback) => {
+ event.preventDefault();
+ debug('browser login event', authInfo);
+ if (authInfo.isProxy && authInfo.scheme === 'basic') {
+ webContents.send('get-service-id');
+
+ ipcMain.on('service-id', (e, id) => {
+ debug('Received service id', id);
+
+ const ps = proxySettings.get(id);
+ callback(ps.user, ps.password);
+ });
+ } else {
+ // TODO: implement basic auth
+ }
+});
+
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// 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 {
@observable hasCrashed = false;
@observable isDarkModeEnabled = false;
- // @observable proxy = {
- // isEnabled: false,
- // };
-
constructor(data, recipe) {
if (!data) {
console.error('Service config not valid');
@@ -73,6 +69,8 @@ export default class Service {
this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon;
+ this.proxy = data.proxy !== undefined ? data.proxy : this.proxy;
+
this.recipe = recipe;
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';
export default class Settings {
@observable app = DEFAULT_APP_SETTINGS
+ @observable proxy = {}
+
@observable service = {
activeService: '',
}
- @observable group = {
- collapsed: [],
- disabled: [],
- }
-
@observable stats = {
appStarts: 0,
}
@observable migration = {}
- constructor({ app, service, group, stats, migration }) {
+ constructor({ app, proxy, service, stats, migration }) {
Object.assign(this.app, app);
+ Object.assign(this.proxy, proxy);
Object.assign(this.service, service);
- Object.assign(this.group, group);
Object.assign(this.stats, stats);
Object.assign(this.migration, migration);
}
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';
import delayApp from '../features/delayApp';
import spellchecker from '../features/spellchecker';
+import serviceProxy from '../features/serviceProxy';
export default class FeaturesStore extends Store {
@observable defaultFeaturesRequest = new CachedRequest(this.api.features, 'default');
@@ -38,5 +39,6 @@ export default class FeaturesStore extends Store {
_enableFeatures() {
delayApp(this.stores, this.actions);
spellchecker(this.stores, this.actions);
+ serviceProxy(this.stores, this.actions);
}
}
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 {
// Actions
@action async _createService({ recipeId, serviceData, redirect = true }) {
const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
+
const response = await this.createServiceRequest.execute(recipeId, data)._promise;
this.allServicesRequest.patch((result) => {
@@ -150,6 +151,13 @@ export default class ServicesStore extends Store {
result.push(response.data);
});
+ this.actions.settings.update({
+ type: 'proxy',
+ data: {
+ [`${response.data.id}`]: data.proxy,
+ },
+ });
+
this.actionStatus = response.status || [];
if (redirect) {
@@ -222,6 +230,13 @@ export default class ServicesStore extends Store {
});
}
+ this.actions.settings.update({
+ type: 'proxy',
+ data: {
+ [`${serviceId}`]: data.proxy,
+ },
+ });
+
if (redirect) {
this.stores.router.push('/settings/services');
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 @@
-import { remote } from 'electron';
+import { remote, ipcRenderer } from 'electron';
import { action, computed, observable } from 'mobx';
import localStorage from 'mobx-localstorage';
@@ -7,6 +7,8 @@ import SettingsModel from '../models/Settings';
import Request from './lib/Request';
import CachedRequest from './lib/CachedRequest';
+import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES } from '../config';
+
const { systemPreferences } = remote;
const debug = require('debug')('Franz:SettingsStore');
@@ -14,12 +16,35 @@ export default class SettingsStore extends Store {
@observable appSettingsRequest = new CachedRequest(this.api.local, 'getAppSettings');
@observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings');
+ @observable fileSystemSettingsRequests = [];
+
+ fileSystemSettingsTypes = FILE_SYSTEM_SETTINGS_TYPES;
+ @observable _fileSystemSettingsCache = {
+ app: DEFAULT_APP_SETTINGS,
+ proxy: {},
+ };
+
constructor(...args) {
super(...args);
// Register action handlers
this.actions.settings.update.listen(this._update.bind(this));
this.actions.settings.remove.listen(this._remove.bind(this));
+
+ this.fileSystemSettingsTypes.forEach((type) => {
+ this.fileSystemSettingsRequests[type] = new CachedRequest(this.api.local, 'getAppSettings');
+ });
+
+ ipcRenderer.on('appSettings', (event, resp) => {
+ debug('Get appSettings resolves', resp, resp.type, resp.data);
+
+ this._fileSystemSettingsCache[resp.type] = resp.data;
+ });
+
+ this.fileSystemSettingsTypes.forEach((type) => {
+ console.log(type);
+ ipcRenderer.send('getAppSettings', type);
+ });
}
async setup() {
@@ -28,29 +53,53 @@ export default class SettingsStore extends Store {
await this._migrate();
}
+ @computed get app() {
+ return this._fileSystemSettingsCache.app || DEFAULT_APP_SETTINGS;
+ }
+
+ @computed get proxy() {
+ return this._fileSystemSettingsCache.proxy || {};
+ }
+
+ @computed get service() {
+ return localStorage.getItem('service') || {
+ activeService: '',
+ };
+ }
+
+ @computed get stats() {
+ return localStorage.getItem('stats') || {
+ activeService: '',
+ };
+ }
+
+ @computed get migration() {
+ return localStorage.getItem('migration') || {};
+ }
+
@computed get all() {
- return new SettingsModel({
- app: this.appSettingsRequest.execute().result || {},
- service: localStorage.getItem('service') || {},
- group: localStorage.getItem('group') || {},
- stats: localStorage.getItem('stats') || {},
- migration: localStorage.getItem('migration') || {},
- });
+ return {
+ app: this.app,
+ proxy: this.proxy,
+ service: this.service,
+ stats: this.stats,
+ migration: this.migration,
+ };
}
@action async _update({ type, data }) {
const appSettings = this.all;
- if (type !== 'app') {
+ if (!this.fileSystemSettingsTypes.includes(type)) {
debug('Update settings', type, data, this.all);
localStorage.setItem(type, Object.assign(appSettings[type], data));
} else {
debug('Update settings on file system', type, data);
- this.updateAppSettingsRequest.execute(data);
-
- this.appSettingsRequest.patch((result) => {
- if (!result) return;
- Object.assign(result, data);
+ ipcRenderer.send('updateAppSettings', {
+ type,
+ data,
});
+
+ Object.assign(this._fileSystemSettingsCache[type], data);
}
}
@@ -128,4 +177,8 @@ export default class SettingsStore extends Store {
debug('Set up dark mode');
}
}
+
+ _getFileBasedSettings(type) {
+ ipcRenderer.send('getAppSettings', type);
+ }
}
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 @@
margin: 25px 0 15px;
&:first-of-type { margin-top: 0; }
+
+ .badge {
+ font-weight: normal;
+ margin-left: 10px;
+ }
}
}
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) => {
}
});
-// initSpellchecker
+// Needed for current implementation of electrons 'login' event
+ipcRenderer.on('get-service-id', (event) => {
+ debug('Asking for service id', event);
+
+ event.sender.send('service-id', serviceData.id);
+});
+
document.addEventListener('DOMContentLoaded', () => {
ipcRenderer.sendToHost('hello');