import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { inject, observer } from 'mobx-react';
import { defineMessages, intlShape } from 'react-intl';
import { RouterStore } from 'mobx-react-router';
import UserStore from '../../stores/UserStore';
import RecipesStore from '../../stores/RecipesStore';
import ServicesStore from '../../stores/ServicesStore';
import SettingsStore from '../../stores/SettingsStore';
import FeaturesStore from '../../stores/FeaturesStore';
import Form from '../../lib/Form';
import ServiceError from '../../components/settings/services/ServiceError';
import EditServiceForm from '../../components/settings/services/EditServiceForm';
import ErrorBoundary from '../../components/util/ErrorBoundary';
import { required, url, oneRequired } from '../../helpers/validation-helpers';
import { getSelectOptions } from '../../helpers/i18n-helpers';
import { config as proxyFeature } from '../../features/serviceProxy';
import { SPELLCHECKER_LOCALES } from '../../i18n/languages';
import globalMessages from '../../i18n/globalMessages';
const messages = defineMessages({
name: {
id: 'settings.service.form.name',
defaultMessage: '!!!Name',
},
enableService: {
id: 'settings.service.form.enableService',
defaultMessage: '!!!Enable service',
},
enableHibernation: {
id: 'settings.service.form.enableHibernation',
defaultMessage: '!!!Enable hibernation',
},
enableNotification: {
id: 'settings.service.form.enableNotification',
defaultMessage: '!!!Enable Notifications',
},
enableBadge: {
id: 'settings.service.form.enableBadge',
defaultMessage: '!!!Show unread message badges',
},
enableAudio: {
id: 'settings.service.form.enableAudio',
defaultMessage: '!!!Enable audio',
},
team: {
id: 'settings.service.form.team',
defaultMessage: '!!!Team',
},
customUrl: {
id: 'settings.service.form.customUrl',
defaultMessage: '!!!Service URL',
},
indirectMessages: {
id: 'settings.service.form.indirectMessages',
defaultMessage: '!!!Show message badge for all new messages',
},
icon: {
id: 'settings.service.form.icon',
defaultMessage: '!!!Custom icon',
},
enableDarkMode: {
id: 'settings.service.form.enableDarkMode',
defaultMessage: '!!!Enable Dark Mode',
},
darkReaderBrightness: {
id: 'settings.service.form.darkReaderBrightness',
defaultMessage: '!!!Dark Reader Brightness',
},
darkReaderContrast: {
id: 'settings.service.form.darkReaderContrast',
defaultMessage: '!!!Dark Reader Contrast',
},
darkReaderSepia: {
id: 'settings.service.form.darkReaderSepia',
defaultMessage: '!!!Dark Reader Sepia',
},
enableProxy: {
id: 'settings.service.form.proxy.isEnabled',
defaultMessage: '!!!Use Proxy',
},
proxyHost: {
id: 'settings.service.form.proxy.host',
defaultMessage: '!!!Proxy Host/IP',
},
proxyPort: {
id: 'settings.service.form.proxy.port',
defaultMessage: '!!!Port',
},
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 {
static contextTypes = {
intl: intlShape,
};
onSubmit(data) {
const { action } = this.props.router.params;
const { recipes, services } = this.props.stores;
const { createService, updateService } = this.props.actions.service;
data.darkReaderSettings = {
brightness: data.darkReaderBrightness,
contrast: data.darkReaderContrast,
sepia: data.darkReaderSepia,
};
delete data.darkReaderContrast;
delete data.darkReaderBrightness;
delete data.darkReaderSepia;
const serviceData = data;
serviceData.isMuted = !serviceData.isMuted;
if (action === 'edit') {
updateService({ serviceId: services.activeSettings.id, serviceData });
} else {
createService({ recipeId: recipes.active.id, serviceData });
}
}
prepareForm(recipe, service, proxy) {
const {
intl,
} = this.context;
const {
stores,
router,
} = this.props;
const { action } = router.params;
let defaultSpellcheckerLanguage = SPELLCHECKER_LOCALES[stores.settings.app.spellcheckerLanguage];
if (stores.settings.app.spellcheckerLanguage === 'automatic') {
defaultSpellcheckerLanguage = intl.formatMessage(globalMessages.spellcheckerAutomaticDetectionShort);
}
const spellcheckerLanguage = getSelectOptions({
locales: SPELLCHECKER_LOCALES,
resetToDefaultText: intl.formatMessage(globalMessages.spellcheckerSystemDefault, { default: defaultSpellcheckerLanguage }),
automaticDetectionText: stores.settings.app.spellcheckerLanguage !== 'automatic' ? intl.formatMessage(globalMessages.spellcheckerAutomaticDetection) : '',
});
const config = {
fields: {
name: {
label: intl.formatMessage(messages.name),
placeholder: intl.formatMessage(messages.name),
value: service.id ? service.name : recipe.name,
},
isEnabled: {
label: intl.formatMessage(messages.enableService),
value: service.isEnabled,
default: true,
},
isHibernationEnabled: {
label: intl.formatMessage(messages.enableHibernation),
value: action !== 'edit' ? recipe.autoHibernate : service.isHibernationEnabled,
default: true,
},
isNotificationEnabled: {
label: intl.formatMessage(messages.enableNotification),
value: service.isNotificationEnabled,
default: true,
},
isBadgeEnabled: {
label: intl.formatMessage(messages.enableBadge),
value: service.isBadgeEnabled,
default: true,
},
isMuted: {
label: intl.formatMessage(messages.enableAudio),
value: !service.isMuted,
default: true,
},
customIcon: {
label: intl.formatMessage(messages.icon),
value: service.hasCustomUploadedIcon ? service.icon : false,
default: null,
type: 'file',
},
isDarkModeEnabled: {
label: intl.formatMessage(messages.enableDarkMode),
value: service.isDarkModeEnabled,
default: stores.settings.app.darkMode,
},
darkReaderBrightness: {
label: intl.formatMessage(messages.darkReaderBrightness),
value: service.darkReaderSettings ? service.darkReaderSettings.brightness : undefined,
default: 100,
},
darkReaderContrast: {
label: intl.formatMessage(messages.darkReaderContrast),
value: service.darkReaderSettings ? service.darkReaderSettings.contrast : undefined,
default: 90,
},
darkReaderSepia: {
label: intl.formatMessage(messages.darkReaderSepia),
value: service.darkReaderSettings ? service.darkReaderSettings.sepia : undefined,
default: 10,
},
spellcheckerLanguage: {
label: intl.formatMessage(globalMessages.spellcheckerLanguage),
value: service.spellcheckerLanguage,
options: spellcheckerLanguage,
disabled: !stores.settings.app.enableSpellchecking,
},
userAgentPref: {
label: intl.formatMessage(globalMessages.userAgentPref),
placeholder: service.defaultUserAgent,
value: service.userAgentPref ? service.userAgentPref : '',
},
},
};
if (recipe.hasTeamId) {
Object.assign(config.fields, {
team: {
label: intl.formatMessage(messages.team),
placeholder: intl.formatMessage(messages.team),
value: service.team,
validators: [required],
},
});
}
if (recipe.hasCustomUrl) {
Object.assign(config.fields, {
customUrl: {
label: intl.formatMessage(messages.customUrl),
placeholder: 'https://',
value: service.customUrl || recipe.serviceURL,
validators: [required, url],
},
});
}
// More fine grained and use case specific validation rules
if (recipe.hasTeamId && recipe.hasCustomUrl) {
config.fields.team.validators = [oneRequired(['team', 'customUrl'])];
config.fields.customUrl.validators = [url, oneRequired(['team', 'customUrl'])];
}
// If a service can be hosted and has a teamId or customUrl
if (recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl)) {
if (config.fields.team) {
config.fields.team.validators = [];
}
if (config.fields.customUrl) {
config.fields.customUrl.validators = [url];
}
}
if (recipe.hasIndirectMessages) {
Object.assign(config.fields, {
isIndirectMessageBadgeEnabled: {
label: intl.formatMessage(messages.indirectMessages),
value: service.isIndirectMessageBadgeEnabled,
default: true,
},
});
}
if (proxy.isEnabled) {
const serviceProxyConfig = 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: '',
},
port: {
label: intl.formatMessage(messages.proxyPort),
value: serviceProxyConfig.port,
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);
}
deleteService() {
const { deleteService } = this.props.actions.service;
const { action } = this.props.router.params;
if (action === 'edit') {
const { activeSettings: service } = this.props.stores.services;
deleteService({
serviceId: service.id,
redirect: '/settings/services',
});
}
}
openRecipeFile(file) {
const { openRecipeFile } = this.props.actions.service;
const { action } = this.props.router.params;
if (action === 'edit') {
const { activeSettings: service } = this.props.stores.services;
openRecipeFile({
recipe: service.recipe.id,
file,
});
}
}
render() {
const {
recipes, services, user, settings,
} = this.props.stores;
const { action } = this.props.router.params;
let recipe;
let service = {};
let isLoading = false;
if (action === 'add') {
recipe = recipes.active;
// TODO: render error message when recipe is `null`
if (!recipe) {
return (