aboutsummaryrefslogtreecommitdiffstats
path: root/src/containers/settings/EditServiceScreen.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/containers/settings/EditServiceScreen.tsx')
-rw-r--r--src/containers/settings/EditServiceScreen.tsx480
1 files changed, 480 insertions, 0 deletions
diff --git a/src/containers/settings/EditServiceScreen.tsx b/src/containers/settings/EditServiceScreen.tsx
new file mode 100644
index 000000000..6545c3d7d
--- /dev/null
+++ b/src/containers/settings/EditServiceScreen.tsx
@@ -0,0 +1,480 @@
1import { Component, ReactElement } from 'react';
2import { inject, observer } from 'mobx-react';
3import { defineMessages, injectIntl } from 'react-intl';
4
5import { RouterStore } from 'mobx-react-router';
6import { StoresProps } from 'src/@types/ferdium-components.types';
7import { IRecipe } from 'src/models/Recipe';
8import Service from 'src/models/Service';
9import { FormFields } from 'src/@types/mobx-form.types';
10import Form from '../../lib/Form';
11
12import ServiceError from '../../components/settings/services/ServiceError';
13import EditServiceForm from '../../components/settings/services/EditServiceForm';
14import ErrorBoundary from '../../components/util/ErrorBoundary';
15
16import { required, url, oneRequired } from '../../helpers/validation-helpers';
17import { getSelectOptions } from '../../helpers/i18n-helpers';
18
19import { config as proxyFeature } from '../../features/serviceProxy';
20
21import { SPELLCHECKER_LOCALES } from '../../i18n/languages';
22
23import globalMessages from '../../i18n/globalMessages';
24import { DEFAULT_APP_SETTINGS, DEFAULT_SERVICE_SETTINGS } from '../../config';
25
26const messages = defineMessages({
27 name: {
28 id: 'settings.service.form.name',
29 defaultMessage: 'Name',
30 },
31 enableService: {
32 id: 'settings.service.form.enableService',
33 defaultMessage: 'Enable service',
34 },
35 enableHibernation: {
36 id: 'settings.service.form.enableHibernation',
37 defaultMessage: 'Enable hibernation',
38 },
39 enableWakeUp: {
40 id: 'settings.service.form.enableWakeUp',
41 defaultMessage: 'Enable wake up',
42 },
43 enableNotification: {
44 id: 'settings.service.form.enableNotification',
45 defaultMessage: 'Enable notifications',
46 },
47 enableBadge: {
48 id: 'settings.service.form.enableBadge',
49 defaultMessage: 'Show unread message badges',
50 },
51 enableAudio: {
52 id: 'settings.service.form.enableAudio',
53 defaultMessage: 'Enable audio',
54 },
55 team: {
56 id: 'settings.service.form.team',
57 defaultMessage: 'Team',
58 },
59 customUrl: {
60 id: 'settings.service.form.customUrl',
61 defaultMessage: 'Custom server',
62 },
63 indirectMessages: {
64 id: 'settings.service.form.indirectMessages',
65 defaultMessage: 'Show message badge for all new messages',
66 },
67 icon: {
68 id: 'settings.service.form.icon',
69 defaultMessage: 'Custom icon',
70 },
71 enableDarkMode: {
72 id: 'settings.service.form.enableDarkMode',
73 defaultMessage: 'Enable Dark Mode',
74 },
75 darkReaderBrightness: {
76 id: 'settings.service.form.darkReaderBrightness',
77 defaultMessage: 'Dark Reader Brightness',
78 },
79 darkReaderContrast: {
80 id: 'settings.service.form.darkReaderContrast',
81 defaultMessage: 'Dark Reader Contrast',
82 },
83 darkReaderSepia: {
84 id: 'settings.service.form.darkReaderSepia',
85 defaultMessage: 'Dark Reader Sepia',
86 },
87 enableProgressbar: {
88 id: 'settings.service.form.enableProgressbar',
89 defaultMessage: 'Enable Progress bar',
90 },
91 trapLinkClicks: {
92 id: 'settings.service.form.trapLinkClicks',
93 defaultMessage: 'Open URLs within Ferdium',
94 },
95 onlyShowFavoritesInUnreadCount: {
96 id: 'settings.service.form.onlyShowFavoritesInUnreadCount',
97 defaultMessage: 'Only show Favorites in unread count',
98 },
99 enableProxy: {
100 id: 'settings.service.form.proxy.isEnabled',
101 defaultMessage: 'Use Proxy',
102 },
103 proxyHost: {
104 id: 'settings.service.form.proxy.host',
105 defaultMessage: 'Proxy Host/IP',
106 },
107 proxyPort: {
108 id: 'settings.service.form.proxy.port',
109 defaultMessage: 'Port',
110 },
111 proxyUser: {
112 id: 'settings.service.form.proxy.user',
113 defaultMessage: 'User (optional)',
114 },
115 proxyPassword: {
116 id: 'settings.service.form.proxy.password',
117 defaultMessage: 'Password (optional)',
118 },
119});
120
121interface EditServicesScreenProps extends StoresProps {
122 router: RouterStore;
123 intl: any;
124}
125
126class EditServiceScreen extends Component<EditServicesScreenProps> {
127 onSubmit(data: any) {
128 // @ts-ignore TODO: This is actually there and we don't have a correct type right now.
129 const { action } = this.props.router.params;
130 const { recipes, services } = this.props.stores;
131 const { createService, updateService } = this.props.actions.service;
132 data.darkReaderSettings = {
133 brightness: data.darkReaderBrightness,
134 contrast: data.darkReaderContrast,
135 sepia: data.darkReaderSepia,
136 };
137 delete data.darkReaderContrast;
138 delete data.darkReaderBrightness;
139 delete data.darkReaderSepia;
140
141 const serviceData = data;
142 serviceData.isMuted = !serviceData.isMuted;
143
144 if (action === 'edit') {
145 updateService({ serviceId: services.activeSettings?.id, serviceData });
146 } else {
147 createService({ recipeId: recipes.active?.id, serviceData });
148 }
149 }
150
151 prepareForm(recipe: IRecipe, service: Service | null, proxy: any): Form {
152 const { intl } = this.props;
153
154 const { stores, router } = this.props;
155
156 // @ts-ignore TODO: This is actually there and we don't have a correct type right now.
157 const { action } = router.params;
158
159 let defaultSpellcheckerLanguage =
160 SPELLCHECKER_LOCALES[stores.settings.app.spellcheckerLanguage];
161
162 if (stores.settings.app.spellcheckerLanguage === 'automatic') {
163 defaultSpellcheckerLanguage = intl.formatMessage(
164 globalMessages.spellcheckerAutomaticDetectionShort,
165 );
166 }
167
168 const spellcheckerLanguage = getSelectOptions({
169 locales: SPELLCHECKER_LOCALES,
170 resetToDefaultText: intl.formatMessage(
171 globalMessages.spellcheckerSystemDefault,
172 { default: defaultSpellcheckerLanguage },
173 ),
174 automaticDetectionText:
175 stores.settings.app.spellcheckerLanguage !== 'automatic'
176 ? intl.formatMessage(globalMessages.spellcheckerAutomaticDetection)
177 : '',
178 });
179
180 const config: FormFields = {
181 fields: {
182 name: {
183 label: intl.formatMessage(messages.name),
184 placeholder: intl.formatMessage(messages.name),
185 value: service?.id ? service.name : recipe.name,
186 },
187 isEnabled: {
188 label: intl.formatMessage(messages.enableService),
189 value: service?.isEnabled,
190 default: DEFAULT_SERVICE_SETTINGS.isEnabled,
191 },
192 isHibernationEnabled: {
193 label: intl.formatMessage(messages.enableHibernation),
194 value:
195 action !== 'edit'
196 ? recipe.autoHibernate
197 : service?.isHibernationEnabled,
198 default: DEFAULT_SERVICE_SETTINGS.isHibernationEnabled,
199 },
200 isWakeUpEnabled: {
201 label: intl.formatMessage(messages.enableWakeUp),
202 value: service?.isWakeUpEnabled,
203 default: DEFAULT_SERVICE_SETTINGS.isWakeUpEnabled,
204 },
205 isNotificationEnabled: {
206 label: intl.formatMessage(messages.enableNotification),
207 value: service?.isNotificationEnabled,
208 default: DEFAULT_SERVICE_SETTINGS.isNotificationEnabled,
209 },
210 isBadgeEnabled: {
211 label: intl.formatMessage(messages.enableBadge),
212 value: service?.isBadgeEnabled,
213 default: DEFAULT_SERVICE_SETTINGS.isBadgeEnabled,
214 },
215 trapLinkClicks: {
216 label: intl.formatMessage(messages.trapLinkClicks),
217 value: service?.trapLinkClicks,
218 default: DEFAULT_SERVICE_SETTINGS.trapLinkClicks,
219 },
220 isMuted: {
221 label: intl.formatMessage(messages.enableAudio),
222 value: !service?.isMuted,
223 default: DEFAULT_SERVICE_SETTINGS.isMuted,
224 },
225 customIcon: {
226 label: intl.formatMessage(messages.icon),
227 value: service?.hasCustomUploadedIcon ? service?.icon : false,
228 default: null,
229 type: 'file',
230 },
231 isDarkModeEnabled: {
232 label: intl.formatMessage(messages.enableDarkMode),
233 value: service?.isDarkModeEnabled,
234 default: stores.settings.app.darkMode,
235 },
236 darkReaderBrightness: {
237 label: intl.formatMessage(messages.darkReaderBrightness),
238 value: service?.darkReaderSettings
239 ? service?.darkReaderSettings.brightness
240 : undefined,
241 default: 100,
242 },
243 darkReaderContrast: {
244 label: intl.formatMessage(messages.darkReaderContrast),
245 value: service?.darkReaderSettings
246 ? service?.darkReaderSettings.contrast
247 : undefined,
248 default: 90,
249 },
250 darkReaderSepia: {
251 label: intl.formatMessage(messages.darkReaderSepia),
252 value: service?.darkReaderSettings
253 ? service?.darkReaderSettings.sepia
254 : undefined,
255 default: 10,
256 },
257 isProgressbarEnabled: {
258 label: intl.formatMessage(messages.enableProgressbar),
259 value: service?.isProgressbarEnabled,
260 default: DEFAULT_SERVICE_SETTINGS.isProgressbarEnabled,
261 },
262 spellcheckerLanguage: {
263 label: intl.formatMessage(globalMessages.spellcheckerLanguage),
264 value: service?.spellcheckerLanguage,
265 options: spellcheckerLanguage,
266 disabled: !stores.settings.app.enableSpellchecking,
267 },
268 userAgentPref: {
269 label: intl.formatMessage(globalMessages.userAgentPref),
270 placeholder: service?.defaultUserAgent,
271 value: service?.userAgentPref ? service.userAgentPref : '',
272 },
273 },
274 };
275
276 if (recipe.hasTeamId) {
277 Object.assign(config.fields, {
278 team: {
279 label: intl.formatMessage(messages.team),
280 placeholder: intl.formatMessage(messages.team),
281 value: service?.team,
282 validators: [required],
283 },
284 });
285 }
286
287 if (recipe.hasCustomUrl) {
288 Object.assign(config.fields, {
289 customUrl: {
290 label: intl.formatMessage(messages.customUrl),
291 placeholder: "'http://' or 'https://' or 'file:///'",
292 value: service?.customUrl || recipe.serviceURL,
293 validators: [required, url],
294 },
295 });
296 }
297
298 // More fine grained and use case specific validation rules
299 if (recipe.hasTeamId && recipe.hasCustomUrl) {
300 config.fields.team.validators = [oneRequired(['team', 'customUrl'])];
301 config.fields.customUrl.validators = [
302 url,
303 oneRequired(['team', 'customUrl']),
304 ];
305 }
306
307 // If a service can be hosted and has a teamId or customUrl
308 if (recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl)) {
309 if (config.fields.team) {
310 config.fields.team.validators = [];
311 }
312 if (config.fields.customUrl) {
313 config.fields.customUrl.validators = [url];
314 }
315 }
316
317 if (recipe.hasIndirectMessages) {
318 Object.assign(config.fields, {
319 isIndirectMessageBadgeEnabled: {
320 label: intl.formatMessage(messages.indirectMessages),
321 value: service?.isIndirectMessageBadgeEnabled,
322 default: DEFAULT_SERVICE_SETTINGS.hasIndirectMessages,
323 },
324 });
325 }
326
327 if (recipe.allowFavoritesDelineationInUnreadCount) {
328 Object.assign(config.fields, {
329 onlyShowFavoritesInUnreadCount: {
330 label: intl.formatMessage(messages.onlyShowFavoritesInUnreadCount),
331 value: service?.onlyShowFavoritesInUnreadCount,
332 default: DEFAULT_APP_SETTINGS.onlyShowFavoritesInUnreadCount,
333 },
334 });
335 }
336
337 if (proxy.isEnabled) {
338 let serviceProxyConfig: {
339 isEnabled?: boolean;
340 host?: string;
341 port?: number;
342 user?: string;
343 password?: string;
344 } = {};
345 if (service) {
346 serviceProxyConfig = stores.settings.proxy[service.id] || {};
347 }
348
349 Object.assign(config.fields, {
350 proxy: {
351 name: 'proxy',
352 label: 'proxy',
353 fields: {
354 isEnabled: {
355 label: intl.formatMessage(messages.enableProxy),
356 value: serviceProxyConfig.isEnabled,
357 default: DEFAULT_APP_SETTINGS.proxyFeatureEnabled,
358 },
359 host: {
360 label: intl.formatMessage(messages.proxyHost),
361 value: serviceProxyConfig.host,
362 default: '',
363 },
364 port: {
365 label: intl.formatMessage(messages.proxyPort),
366 value: serviceProxyConfig.port,
367 default: '',
368 },
369 user: {
370 label: intl.formatMessage(messages.proxyUser),
371 value: serviceProxyConfig.user,
372 default: '',
373 },
374 password: {
375 label: intl.formatMessage(messages.proxyPassword),
376 value: serviceProxyConfig.password,
377 default: '',
378 type: 'password',
379 },
380 },
381 },
382 });
383 }
384
385 // @ts-ignore: Remove this ignore once mobx-react-form v4 with typescript
386 // support has been released.
387 return new Form(config);
388 }
389
390 deleteService(): void {
391 const { deleteService } = this.props.actions.service;
392 // @ts-ignore TODO: This is actually there and we don't have a correct type right now.
393 const { action } = this.props.router.params;
394
395 if (action === 'edit') {
396 const { activeSettings: service } = this.props.stores.services;
397 deleteService({
398 serviceId: service?.id,
399 redirect: '/settings/services',
400 });
401 }
402 }
403
404 openRecipeFile(file: any): void {
405 const { openRecipeFile } = this.props.actions.service;
406 // @ts-ignore TODO: This is actually there and we don't have a correct type right now.
407 const { action } = this.props.router.params;
408
409 if (action === 'edit') {
410 const { activeSettings: service } = this.props.stores.services;
411 openRecipeFile({
412 recipe: service?.recipe.id,
413 file,
414 });
415 }
416 }
417
418 render(): ReactElement {
419 const { recipes, services, user } = this.props.stores;
420
421 // @ts-ignore TODO: This is actually there and we don't have a correct type right now.
422 const { action } = this.props.router.params;
423
424 let recipe: null | IRecipe = null;
425 let service: null | Service = null;
426 let isLoading = false;
427
428 if (action === 'add') {
429 recipe = recipes.active;
430
431 // TODO: render error message when recipe is `null`
432 if (!recipe) {
433 return <ServiceError />;
434 }
435 } else {
436 service = services.activeSettings;
437 isLoading = services.allServicesRequest.isExecuting;
438
439 if (!isLoading && service) {
440 recipe = service.recipe;
441 }
442 }
443
444 if (isLoading) {
445 return <div>Loading...</div>;
446 }
447
448 if (!recipe) {
449 return <div>something went wrong</div>;
450 }
451
452 const form = this.prepareForm(recipe, service, proxyFeature);
453
454 return (
455 <ErrorBoundary>
456 <EditServiceForm
457 action={action}
458 recipe={recipe}
459 service={service}
460 user={user.data}
461 form={form}
462 status={services.actionStatus}
463 isSaving={
464 services.updateServiceRequest.isExecuting ||
465 services.createServiceRequest.isExecuting
466 }
467 isDeleting={services.deleteServiceRequest.isExecuting}
468 onSubmit={d => this.onSubmit(d)}
469 onDelete={() => this.deleteService()}
470 openRecipeFile={file => this.openRecipeFile(file)}
471 isProxyFeatureEnabled={proxyFeature.isEnabled}
472 />
473 </ErrorBoundary>
474 );
475 }
476}
477
478export default injectIntl<'intl', EditServicesScreenProps>(
479 inject('stores', 'actions')(observer(EditServiceScreen)),
480);