aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/settings/services/EditServiceForm.tsx
diff options
context:
space:
mode:
authorLibravatar Muhamed <unknown>2022-11-08 07:40:02 +0530
committerLibravatar Vijay Aravamudhan <vraravam@users.noreply.github.com>2022-11-08 17:25:27 +0530
commit570d26baf9e4ad87a8c0752b5edfb5c441bf9d80 (patch)
treeee0a0e6bec7d10ec438b5269537905fee80c377b /src/components/settings/services/EditServiceForm.tsx
parentrefactor: remove toggle component's duplicate (diff)
downloadferdium-app-570d26baf9e4ad87a8c0752b5edfb5c441bf9d80.tar.gz
ferdium-app-570d26baf9e4ad87a8c0752b5edfb5c441bf9d80.tar.zst
ferdium-app-570d26baf9e4ad87a8c0752b5edfb5c441bf9d80.zip
fix: slack issue caused by input TS conversion
Diffstat (limited to 'src/components/settings/services/EditServiceForm.tsx')
-rw-r--r--src/components/settings/services/EditServiceForm.tsx540
1 files changed, 540 insertions, 0 deletions
diff --git a/src/components/settings/services/EditServiceForm.tsx b/src/components/settings/services/EditServiceForm.tsx
new file mode 100644
index 000000000..b594fcd40
--- /dev/null
+++ b/src/components/settings/services/EditServiceForm.tsx
@@ -0,0 +1,540 @@
1import { Component, FormEvent, ReactElement } from 'react';
2import { observer } from 'mobx-react';
3import { Link } from 'react-router-dom';
4import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl';
5import normalizeUrl from 'normalize-url';
6import { mdiInformation } from '@mdi/js';
7import Form from '../../../lib/Form';
8import Tabs from '../../ui/Tabs/Tabs';
9import TabItem from '../../ui/Tabs/TabItem';
10import Input from '../../ui/input/index';
11import Toggle from '../../ui/toggle';
12import Slider from '../../ui/Slider';
13import Button from '../../ui/button';
14import ImageUpload from '../../ui/imageUpload';
15import Select from '../../ui/Select';
16import { isMac } from '../../../environment';
17import globalMessages from '../../../i18n/globalMessages';
18import Icon from '../../ui/icon';
19import { H3 } from '../../ui/headline';
20import { IRecipe } from '../../../models/Recipe';
21import Service from '../../../models/Service';
22
23const messages = defineMessages({
24 saveService: {
25 id: 'settings.service.form.saveButton',
26 defaultMessage: 'Save service',
27 },
28 deleteService: {
29 id: 'settings.service.form.deleteButton',
30 defaultMessage: 'Delete service',
31 },
32 openDarkmodeCss: {
33 id: 'settings.service.form.openDarkmodeCss',
34 defaultMessage: 'Open darkmode.css',
35 },
36 openUserCss: {
37 id: 'settings.service.form.openUserCss',
38 defaultMessage: 'Open user.css',
39 },
40 openUserJs: {
41 id: 'settings.service.form.openUserJs',
42 defaultMessage: 'Open user.js',
43 },
44 recipeFileInfo: {
45 id: 'settings.service.form.recipeFileInfo',
46 defaultMessage:
47 '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.',
48 },
49 availableServices: {
50 id: 'settings.service.form.availableServices',
51 defaultMessage: 'Available services',
52 },
53 yourServices: {
54 id: 'settings.service.form.yourServices',
55 defaultMessage: 'Your services',
56 },
57 addServiceHeadline: {
58 id: 'settings.service.form.addServiceHeadline',
59 defaultMessage: 'Add {name}',
60 },
61 editServiceHeadline: {
62 id: 'settings.service.form.editServiceHeadline',
63 defaultMessage: 'Edit {name}',
64 },
65 tabHosted: {
66 id: 'settings.service.form.tabHosted',
67 defaultMessage: 'Hosted',
68 },
69 tabOnPremise: {
70 id: 'settings.service.form.tabOnPremise',
71 defaultMessage: 'Self hosted ⭐️',
72 },
73 useHostedService: {
74 id: 'settings.service.form.useHostedService',
75 defaultMessage: 'Use the hosted {name} service.',
76 },
77 customUrlValidationError: {
78 id: 'settings.service.form.customUrlValidationError',
79 defaultMessage: 'Could not validate custom {name} server.',
80 },
81 indirectMessageInfo: {
82 id: 'settings.service.form.indirectMessageInfo',
83 defaultMessage:
84 'You will be notified about all new messages in a channel, not just @username, @channel, @here, ...',
85 },
86 isMutedInfo: {
87 id: 'settings.service.form.isMutedInfo',
88 defaultMessage:
89 'When disabled, all notification sounds and audio playback are muted',
90 },
91 isHibernationEnabledInfo: {
92 id: 'settings.service.form.isHibernatedEnabledInfo',
93 defaultMessage:
94 'When enabled, a service will be shut down after a period of time to save system resources.',
95 },
96 headlineNotifications: {
97 id: 'settings.service.form.headlineNotifications',
98 defaultMessage: 'Notifications',
99 },
100 headlineBadges: {
101 id: 'settings.service.form.headlineBadges',
102 defaultMessage: 'Unread message badges',
103 },
104 headlineGeneral: {
105 id: 'settings.service.form.headlineGeneral',
106 defaultMessage: 'General',
107 },
108 headlineAppearance: {
109 id: 'settings.service.form.headlineAppearance',
110 defaultMessage: 'Appearance',
111 },
112 headlineDarkReaderSettings: {
113 id: 'settings.service.form.headlineDarkReaderSettings',
114 defaultMessage: 'Dark Reader Settings',
115 },
116 iconDelete: {
117 id: 'settings.service.form.iconDelete',
118 defaultMessage: 'Delete',
119 },
120 iconUpload: {
121 id: 'settings.service.form.iconUpload',
122 defaultMessage: 'Drop your image, or click here',
123 },
124 headlineProxy: {
125 id: 'settings.service.form.proxy.headline',
126 defaultMessage: 'HTTP/HTTPS Proxy Settings',
127 },
128 proxyRestartInfo: {
129 id: 'settings.service.form.proxy.restartInfo',
130 defaultMessage: 'Please restart Ferdium after changing proxy Settings.',
131 },
132 proxyInfo: {
133 id: 'settings.service.form.proxy.info',
134 defaultMessage:
135 'Proxy settings will not be synchronized with the Ferdium servers.',
136 },
137 serviceReloadRequired: {
138 id: 'settings.service.reloadRequired',
139 defaultMessage: 'Changes require reload of the service',
140 },
141 maxFileSize: {
142 id: 'settings.service.form.maxFileSize',
143 defaultMessage: 'Maximum filesize:',
144 },
145 maxFileSizeError: {
146 id: 'settings.service.form.maxFileSizeError',
147 defaultMessage: 'The file you are trying to submit is too large.',
148 },
149});
150
151interface IProps extends WrappedComponentProps {
152 recipe: IRecipe;
153 service: Service | null;
154 action?: string;
155 form: Form;
156 onSubmit: (...args: any[]) => void;
157 onDelete: () => void;
158 openRecipeFile: (recipeFile: string) => void;
159 isSaving: boolean;
160 isDeleting: boolean;
161 isProxyFeatureEnabled: boolean;
162}
163
164interface IState {
165 isValidatingCustomUrl: boolean;
166}
167
168@observer
169class EditServiceForm extends Component<IProps, IState> {
170 constructor(props: IProps) {
171 super(props);
172
173 this.state = {
174 isValidatingCustomUrl: false,
175 };
176 }
177
178 submit(e: FormEvent): void {
179 const { recipe } = this.props;
180
181 e.preventDefault();
182 this.props.form.submit({
183 onSuccess: async form => {
184 const values = form.values();
185 let isValid = true;
186
187 const { files } = form.$('customIcon');
188 if (files) {
189 const [iconFile] = files;
190 values.iconFile = iconFile;
191 }
192
193 if (recipe.validateUrl && values.customUrl) {
194 this.setState({ isValidatingCustomUrl: true });
195 try {
196 values.customUrl = normalizeUrl(values.customUrl, {
197 stripAuthentication: false,
198 stripWWW: false,
199 removeTrailingSlash: false,
200 });
201 isValid = await recipe.validateUrl(values.customUrl);
202 } catch (error) {
203 console.warn('ValidateURL', error);
204 isValid = false;
205 }
206 }
207
208 if (isValid) {
209 this.props.onSubmit(values);
210 } else {
211 form.invalidate('url-validation-error');
212 }
213
214 this.setState({ isValidatingCustomUrl: false });
215 },
216 onError: () => {},
217 });
218 }
219
220 render(): ReactElement {
221 const {
222 recipe,
223 service = {} as Service,
224 action = '',
225 form,
226 isSaving,
227 isDeleting,
228 onDelete,
229 openRecipeFile,
230 isProxyFeatureEnabled,
231 intl,
232 } = this.props;
233 const { isValidatingCustomUrl } = this.state;
234
235 const deleteButton = isDeleting ? (
236 <Button
237 label={intl.formatMessage(messages.deleteService)}
238 loaded={false}
239 buttonType="secondary"
240 className="settings__delete-button"
241 disabled
242 />
243 ) : (
244 <Button
245 buttonType="danger"
246 label={intl.formatMessage(messages.deleteService)}
247 className="settings__delete-button"
248 onClick={onDelete}
249 />
250 );
251
252 let activeTabIndex = 0;
253 if (recipe.hasHostedOption && service?.team) {
254 activeTabIndex = 1;
255 } else if (recipe.hasHostedOption && service?.customUrl) {
256 activeTabIndex = 2;
257 }
258
259 const requiresUserInput =
260 !recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl);
261
262 return (
263 <div className="settings__main">
264 <div className="settings__header">
265 <span className="settings__header-item">
266 {action === 'add' ? (
267 <Link to="/settings/recipes">
268 {intl.formatMessage(messages.availableServices)}
269 </Link>
270 ) : (
271 <Link to="/settings/services">
272 {intl.formatMessage(messages.yourServices)}
273 </Link>
274 )}
275 </span>
276 <span className="separator" />
277 <span className="settings__header-item">
278 {action === 'add'
279 ? intl.formatMessage(messages.addServiceHeadline, {
280 name: recipe.name,
281 })
282 : intl.formatMessage(messages.editServiceHeadline, {
283 name:
284 service && service.name !== '' ? service.name : recipe.name,
285 })}
286 </span>
287 </div>
288 <div className="settings__body">
289 <form onSubmit={e => this.submit(e)} id="form">
290 <div className="service-name">
291 <Input {...form.$('name').bind()} focus />
292 </div>
293 {(recipe.hasTeamId || recipe.hasCustomUrl) && (
294 <Tabs active={activeTabIndex}>
295 {recipe.hasHostedOption && (
296 <TabItem
297 // title={recipe.name} // TODO - [TS DEBT] property not used inside TabItem need to check it
298 >
299 {intl.formatMessage(messages.useHostedService, {
300 name: recipe.name,
301 })}
302 </TabItem>
303 )}
304 {recipe.hasTeamId && (
305 <TabItem
306 // title={intl.formatMessage(messages.tabHosted)} // TODO - [TS DEBT] property not used inside TabItem need to check it
307 >
308 <Input
309 {...form.$('team').bind()}
310 prefix={recipe.urlInputPrefix}
311 suffix={recipe.urlInputSuffix}
312 />
313 </TabItem>
314 )}
315 {recipe.hasCustomUrl && (
316 <TabItem
317 // title={intl.formatMessage(messages.tabOnPremise)} // TODO - [TS DEBT] property not used inside TabItem need to check it
318 >
319 <Input {...form.$('customUrl').bind()} />
320 {form.error === 'url-validation-error' && (
321 <p className="franz-form__error">
322 {intl.formatMessage(messages.customUrlValidationError, {
323 name: recipe.name,
324 })}
325 </p>
326 )}
327 </TabItem>
328 )}
329 </Tabs>
330 )}
331
332 {recipe.message && (
333 <p
334 className="settings__message"
335 style={{
336 marginTop: 0,
337 }}
338 >
339 <Icon icon={mdiInformation} />
340 {recipe.message}
341 </p>
342 )}
343 <div className="service-flex-grid">
344 <div className="settings__options">
345 <div className="settings__settings-group">
346 <H3>{intl.formatMessage(messages.headlineNotifications)}</H3>
347 <Toggle {...form.$('isNotificationEnabled').bind()} />
348 <Toggle {...form.$('isMuted').bind()} />
349 <p className="settings__help indented__help">
350 {intl.formatMessage(messages.isMutedInfo)}
351 </p>
352 <Toggle {...form.$('isMediaBadgeEnabled').bind()} />
353 </div>
354
355 <div className="settings__settings-group">
356 <H3>{intl.formatMessage(messages.headlineBadges)}</H3>
357 <Toggle {...form.$('isBadgeEnabled').bind()} />
358 {recipe.hasIndirectMessages &&
359 form.$('isBadgeEnabled').value && (
360 <>
361 <Toggle
362 {...form.$('isIndirectMessageBadgeEnabled').bind()}
363 />
364 <p className="settings__help indented__help">
365 {intl.formatMessage(messages.indirectMessageInfo)}
366 </p>
367 </>
368 )}
369 {recipe.allowFavoritesDelineationInUnreadCount && (
370 <Toggle
371 {...form.$('onlyShowFavoritesInUnreadCount').bind()}
372 />
373 )}
374 </div>
375
376 <div className="settings__settings-group">
377 <H3>{intl.formatMessage(messages.headlineGeneral)}</H3>
378 <Toggle {...form.$('isEnabled').bind()} />
379 <Toggle {...form.$('isHibernationEnabled').bind()} />
380 <p className="settings__help indented__help">
381 {intl.formatMessage(messages.isHibernationEnabledInfo)}
382 </p>
383 <Toggle {...form.$('isWakeUpEnabled').bind()} />
384 <Toggle {...form.$('trapLinkClicks').bind()} />
385 {/* TODO: Need to figure out how to effect this change without a reload of the recipe */}
386 <p className="settings__help indented__help">
387 {intl.formatMessage(messages.serviceReloadRequired)}
388 </p>
389 </div>
390
391 <div className="settings__settings-group">
392 <H3>{intl.formatMessage(messages.headlineAppearance)}</H3>
393 <Toggle {...form.$('isDarkModeEnabled').bind()} />
394 {form.$('isDarkModeEnabled').value && (
395 <>
396 <H3>
397 {intl.formatMessage(
398 messages.headlineDarkReaderSettings,
399 )}
400 </H3>
401 <Slider field={form.$('darkReaderBrightness')} />
402 <Slider field={form.$('darkReaderContrast')} />
403 <Slider field={form.$('darkReaderSepia')} />
404 </>
405 )}
406 <Toggle {...form.$('isProgressbarEnabled').bind()} />
407 </div>
408 </div>
409 <div className="service-icon">
410 <ImageUpload
411 {...form.$('customIcon').bind()}
412 textDelete={intl.formatMessage(messages.iconDelete)}
413 textUpload={intl.formatMessage(messages.iconUpload)}
414 maxSize={2_097_152}
415 maxFiles={1}
416 textMaxFileSize={intl.formatMessage(messages.maxFileSize)}
417 textMaxFileSizeError={intl.formatMessage(
418 messages.maxFileSizeError,
419 )}
420 />
421 </div>
422 </div>
423
424 {!isMac && (
425 <div className="settings__settings-group">
426 <Select field={form.$('spellcheckerLanguage')} />
427 </div>
428 )}
429
430 {isProxyFeatureEnabled && (
431 <div className="settings__settings-group">
432 <H3>
433 {intl.formatMessage(messages.headlineProxy)}
434 <span className="badge badge--success">beta</span>
435 </H3>
436 <Toggle {...form.$('proxy.isEnabled').bind()} />
437 {form.$('proxy.isEnabled').value && (
438 <>
439 <div className="grid">
440 <div className="grid__row">
441 <Input
442 {...form.$('proxy.host').bind()}
443 className="proxyHost"
444 />
445 <Input {...form.$('proxy.port').bind()} />
446 </div>
447 </div>
448 <div className="grid">
449 <div className="grid__row">
450 <Input {...form.$('proxy.user').bind()} />
451 <Input
452 {...form.$('proxy.password').bind()}
453 showPasswordToggle
454 />
455 </div>
456 </div>
457 <p>
458 <Icon icon={mdiInformation} />
459 {intl.formatMessage(messages.proxyRestartInfo)}
460 </p>
461 <p>
462 <Icon icon={mdiInformation} />
463 {intl.formatMessage(messages.proxyInfo)}
464 </p>
465 </>
466 )}
467 </div>
468 )}
469
470 <div className="user-agent">
471 <Input {...form.$('userAgentPref').bind()} />
472 <p className="settings__help">
473 {intl.formatMessage(globalMessages.userAgentHelp)}
474 </p>
475 </div>
476 </form>
477
478 {action === 'edit' && (
479 <>
480 <div className="settings__open-recipe-file-container">
481 <Button
482 buttonType="secondary"
483 label={intl.formatMessage(messages.openDarkmodeCss)}
484 className="settings__open-recipe-file-button"
485 onClick={() => openRecipeFile('darkmode.css')}
486 />
487 <Button
488 buttonType="secondary"
489 label={intl.formatMessage(messages.openUserCss)}
490 className="settings__open-recipe-file-button"
491 onClick={() => openRecipeFile('user.css')}
492 />
493 <Button
494 buttonType="secondary"
495 label={intl.formatMessage(messages.openUserJs)}
496 className="settings__open-recipe-file-button"
497 onClick={() => openRecipeFile('user.js')}
498 />
499 </div>
500 <p style={{ marginTop: 10, marginBottom: 10 }}>
501 <Icon icon={mdiInformation} />
502 {intl.formatMessage(messages.recipeFileInfo)}
503 </p>
504 </>
505 )}
506 <span style={{ fontStyle: 'italic', fontSize: '80%' }}>
507 Recipe version:
508 {recipe.version}
509 </span>
510 </div>
511 <div className="settings__controls">
512 {/* Delete Button */}
513 <div>{action === 'edit' && deleteButton}</div>
514
515 {/* Save Button */}
516 {isSaving || isValidatingCustomUrl ? (
517 <Button
518 type="submit"
519 label={intl.formatMessage(messages.saveService)}
520 loaded={false}
521 buttonType="secondary"
522 disabled
523 />
524 ) : (
525 <Button
526 type="submit"
527 label={intl.formatMessage(messages.saveService)}
528 htmlForm="form"
529 disabled={
530 action !== 'edit' && form.isPristine && requiresUserInput
531 }
532 />
533 )}
534 </div>
535 </div>
536 );
537 }
538}
539
540export default injectIntl(EditServiceForm);