import { Component } from 'react';
import PropTypes from 'prop-types';
import { inject, observer } from 'mobx-react';
import { defineMessages, injectIntl } 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: 'Custom server',
},
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',
},
onlyShowFavoritesInUnreadCount: {
id: 'settings.service.form.onlyShowFavoritesInUnreadCount',
defaultMessage: 'Only show Favorites in unread count',
},
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 (optional)',
},
proxyPassword: {
id: 'settings.service.form.proxy.password',
defaultMessage: 'Password (optional)',
},
});
@inject('stores', 'actions')
@observer
class EditServiceScreen extends Component {
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.props;
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 (recipe.allowFavoritesDelineationInUnreadCount) {
Object.assign(config.fields, {
onlyShowFavoritesInUnreadCount: {
label: intl.formatMessage(messages.onlyShowFavoritesInUnreadCount),
value: service.onlyShowFavoritesInUnreadCount,
default: false,
},
});
}
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 } = 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