import { mdiInformation } from '@mdi/js'; import { noop } from 'lodash'; import { observer } from 'mobx-react'; import { Component, type FormEvent, type ReactElement } from 'react'; import { type WrappedComponentProps, defineMessages, injectIntl, } from 'react-intl'; import { Link } from 'react-router-dom'; import { isMac } from '../../../environment'; import { normalizedUrl } from '../../../helpers/url-helpers'; import globalMessages from '../../../i18n/globalMessages'; import type Form from '../../../lib/Form'; import type { IRecipe } from '../../../models/Recipe'; import type Service from '../../../models/Service'; import Select from '../../ui/Select'; import Slider from '../../ui/Slider'; import TabItem from '../../ui/Tabs/TabItem'; import Tabs from '../../ui/Tabs/Tabs'; import Button from '../../ui/button'; import { H3 } from '../../ui/headline'; import Icon from '../../ui/icon'; import ImageUpload from '../../ui/imageUpload'; import Input from '../../ui/input/index'; import Toggle from '../../ui/toggle'; const messages = defineMessages({ saveService: { id: 'settings.service.form.saveButton', defaultMessage: 'Save service', }, deleteService: { id: 'settings.service.form.deleteButton', defaultMessage: 'Delete service', }, openDarkmodeCss: { id: 'settings.service.form.openDarkmodeCss', defaultMessage: 'Open darkmode.css', }, openUserCss: { id: 'settings.service.form.openUserCss', defaultMessage: 'Open user.css', }, openUserJs: { id: 'settings.service.form.openUserJs', defaultMessage: 'Open user.js', }, recipeFileInfo: { id: 'settings.service.form.recipeFileInfo', defaultMessage: 'Your user files will be inserted into the webpage so you can customize services in any way you like. User files are only stored locally and are not transferred to other computers using the same account.', }, availableServices: { id: 'settings.service.form.availableServices', defaultMessage: 'Available services', }, yourServices: { id: 'settings.service.form.yourServices', defaultMessage: 'Your services', }, addServiceHeadline: { id: 'settings.service.form.addServiceHeadline', defaultMessage: 'Add {name}', }, editServiceHeadline: { id: 'settings.service.form.editServiceHeadline', defaultMessage: 'Edit {name}', }, tabHosted: { id: 'settings.service.form.tabHosted', defaultMessage: 'Hosted', }, tabOnPremise: { id: 'settings.service.form.tabOnPremise', defaultMessage: 'Self hosted ⭐️', }, useHostedService: { id: 'settings.service.form.useHostedService', defaultMessage: 'Use the hosted {name} service.', }, customUrlValidationError: { id: 'settings.service.form.customUrlValidationError', defaultMessage: 'Could not validate custom {name} server.', }, indirectMessageInfo: { id: 'settings.service.form.indirectMessageInfo', defaultMessage: 'You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', }, isMutedInfo: { id: 'settings.service.form.isMutedInfo', defaultMessage: 'When disabled, all notification sounds and audio playback are muted', }, isHibernationEnabledInfo: { id: 'settings.service.form.isHibernatedEnabledInfo', defaultMessage: 'When enabled, a service will be shut down after a period of time to save system resources.', }, headlineNotifications: { id: 'settings.service.form.headlineNotifications', defaultMessage: 'Notifications', }, headlineBadges: { id: 'settings.service.form.headlineBadges', defaultMessage: 'Unread message badges', }, headlineGeneral: { id: 'settings.service.form.headlineGeneral', defaultMessage: 'General', }, headlineAppearance: { id: 'settings.service.form.headlineAppearance', defaultMessage: 'Appearance', }, headlineDarkReaderSettings: { id: 'settings.service.form.headlineDarkReaderSettings', defaultMessage: 'Dark Reader Settings', }, iconDelete: { id: 'settings.service.form.iconDelete', defaultMessage: 'Delete', }, iconUpload: { id: 'settings.service.form.iconUpload', defaultMessage: 'Drop your image, or click here', }, headlineProxy: { id: 'settings.service.form.proxy.headline', defaultMessage: 'HTTP/HTTPS Proxy Settings', }, proxyRestartInfo: { id: 'settings.service.form.proxy.restartInfo', defaultMessage: 'Please restart Ferdium after changing proxy Settings.', }, proxyInfo: { id: 'settings.service.form.proxy.info', defaultMessage: 'Proxy settings will not be synchronized with the Ferdium servers.', }, serviceReloadRequired: { id: 'settings.service.reloadRequired', defaultMessage: 'Changes require reload of the service', }, maxFileSize: { id: 'settings.service.form.maxFileSize', defaultMessage: 'Maximum filesize:', }, maxFileSizeError: { id: 'settings.service.form.maxFileSizeError', defaultMessage: 'The file you are trying to submit is too large.', }, }); interface IProps extends WrappedComponentProps { recipe: IRecipe; service: Service | null; action?: string; form: Form; onSubmit: (...args: any[]) => void; onDelete: () => void; onClearCache: () => void; openRecipeFile: (recipeFile: string) => void; isSaving: boolean; isDeleting: boolean; isProxyFeatureEnabled: boolean; } interface IState { isValidatingCustomUrl: boolean; } @observer class EditServiceForm extends Component { constructor(props: IProps) { super(props); this.state = { isValidatingCustomUrl: false, }; } submit(e: FormEvent): void { const { recipe } = this.props; e.preventDefault(); this.props.form.submit({ onSuccess: async form => { const values = form.values(); let isValid = true; const { files } = form.$('customIcon'); if (files) { const [iconFile] = files; values.iconFile = iconFile; } if (recipe.validateUrl && values.customUrl) { this.setState({ isValidatingCustomUrl: true }); try { values.customUrl = normalizedUrl(values.customUrl); isValid = await recipe.validateUrl(values.customUrl); } catch (error) { console.warn('ValidateURL', error); isValid = false; } } if (isValid) { this.props.onSubmit(values); } else { form.invalidate('url-validation-error'); } this.setState({ isValidatingCustomUrl: false }); }, onError: noop, }); } render(): ReactElement { const { recipe, service = {} as Service, action = '', form, isSaving, isDeleting, onDelete, onClearCache, openRecipeFile, isProxyFeatureEnabled, intl, } = this.props; const { isValidatingCustomUrl } = this.state; const deleteButton = isDeleting ? (