Skip to content

Commit c0f58f7

Browse files
Frontend fixes (#233)
* Temp * Fixes editors. * Reuse hooks. * Simplify code. * Simplify code.
1 parent cf6f9e3 commit c0f58f7

File tree

12 files changed

+332
-255
lines changed

12 files changed

+332
-255
lines changed

frontend/src/app/framework/react/hooks.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ type QueryState<T> = {
154154

155155
type QueryParams<T> = {
156156
queryKey: string[];
157-
queryFn: (abort: AbortController) => Promise<T>;
157+
queryFn: (abort: AbortSignal) => Promise<T>;
158158
defaultValue: T;
159159
};
160160

@@ -168,7 +168,7 @@ export function useSimpleQuery<T>(params: QueryParams<T>): QueryState<T> {
168168

169169
setState(s => ({ ...s, isLoading: true }));
170170
try {
171-
const value = await params.queryFn(abort);
171+
const value = await params.queryFn(abort.signal);
172172

173173
if (request.current === id) {
174174
setState(s => ({ ...s, error: undefined, value }));
@@ -184,11 +184,11 @@ export function useSimpleQuery<T>(params: QueryParams<T>): QueryState<T> {
184184
}
185185
}
186186

187-
const abort = new AbortController();
188-
loadData(new Date().getTime(), abort);
187+
const controller = new AbortController();
188+
loadData(new Date().getTime(), controller);
189189

190190
return () => {
191-
abort.abort();
191+
controller.abort();
192192
};
193193
}, params.queryKey);
194194

frontend/src/app/pages/email-templates/EmailTemplatePage.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ export const EmailTemplatePage = () => {
3737
const [language, setLanguage] = React.useState(appLanguages[0]);
3838

3939
const properties = useSimpleQuery<TemplatePropertyDto[]>({
40-
queryKey: [],
41-
queryFn: async () => {
42-
const result = await Clients.SmsTemplates.getProperties(appId);
40+
queryKey: [appId],
41+
queryFn: async abort => {
42+
const result = await Clients.SmsTemplates.getProperties(appId, abort);
4343

4444
return result.items;
4545
},

frontend/src/app/pages/email-templates/editor/EmailHtmlEditor.tsx

+59-26
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,90 @@
55
* Copyright (c) Sebastian Stehle. All rights reserved.
66
*/
77

8-
// tslint:disable: quotemark
9-
8+
import * as React from 'react';
109
import Split from 'react-split';
1110
import { Alert } from 'reactstrap';
12-
import { IFrame } from '@app/framework';
11+
import { CodeEditor, CodeEditorProps, IFrame } from '@app/framework';
1312
import { MjmlSchema } from '@app/service';
14-
import { EmailHtmlTextEditor } from './EmailHtmlTextEditor';
15-
import { usePreview } from './helpers';
16-
17-
export interface EmailHtmlEditorProps {
18-
// The value.
19-
value: string;
13+
import { useErrors, usePreview } from './helpers';
14+
import { completeAfter, completeIfAfterLt, completeIfInTag } from './helpers';
15+
import 'codemirror/addon/dialog/dialog';
16+
import 'codemirror/addon/edit/closetag';
17+
import 'codemirror/addon/edit/matchtags';
18+
import 'codemirror/addon/hint/show-hint';
19+
import 'codemirror/addon/hint/xml-hint';
20+
import 'codemirror/addon/lint/lint';
21+
import 'codemirror/addon/scroll/annotatescrollbar';
22+
import 'codemirror/addon/search/jump-to-line';
23+
import 'codemirror/addon/search/match-highlighter';
24+
import 'codemirror/addon/search/matchesonscrollbar';
25+
import 'codemirror/addon/search/search';
26+
import 'codemirror/addon/search/searchcursor';
27+
import 'codemirror/addon/selection/active-line';
28+
import 'codemirror/mode/xml/xml';
2029

30+
export interface EmailHtmlEditorProps extends CodeEditorProps {
2131
// The app name.
2232
appId: string;
2333

2434
// The schema.
2535
schema?: MjmlSchema;
26-
27-
// When the html has changed.
28-
onChange: (value: string) => void;
29-
30-
// Called when the focus has been lost.
31-
onBlur: () => void;
3236
}
3337

3438
export const EmailHtmlEditor = (props: EmailHtmlEditorProps) => {
3539
const {
3640
appId,
37-
onBlur,
38-
onChange,
3941
schema,
4042
value,
43+
...other
4144
} = props;
4245

4346
const emailMarkup = value || '';
4447
const emailPreview = usePreview(appId, emailMarkup, 'Html');
4548

46-
const error = emailPreview.rendering.errors?.[0];
49+
const initialOptions = React.useMemo(() => {
50+
const result: CodeEditorProps['options'] = {
51+
autoCloseTags: true,
52+
mode: 'xml',
53+
extraKeys: {
54+
Tab: (cm) => {
55+
if (cm.getMode().name === 'null') {
56+
cm.execCommand('insertTab');
57+
} else if (cm.somethingSelected()) {
58+
cm.execCommand('indentMore');
59+
} else {
60+
cm.execCommand('insertSoftTab');
61+
}
62+
},
63+
'\'<\'': completeAfter,
64+
'\'/\'': completeIfAfterLt,
65+
'\' \'': completeIfInTag,
66+
'\'=\'': completeIfInTag,
67+
'Ctrl-Space': 'autocomplete',
68+
},
69+
matchTags: true,
70+
};
71+
72+
return result;
73+
}, []);
74+
75+
const { lint, error } = useErrors(emailPreview.rendering);
76+
77+
const options = React.useMemo(() => {
78+
const result: CodeEditorProps['options'] = {
79+
hintOptions: schema ? { schemaInfo: schema } : undefined,
80+
lint,
81+
};
82+
83+
return result;
84+
}, [lint, schema]);
4785

4886
return (
49-
<div className='email-editor'>
87+
<div className='email-editor white'>
5088
<Split direction='horizontal'>
5189
<div className='left'>
52-
<EmailHtmlTextEditor
53-
value={emailMarkup}
54-
errors={emailPreview.rendering?.errors}
55-
onBlur={onBlur}
56-
onChange={onChange}
57-
schema={schema}
58-
/>
90+
<CodeEditor initialOptions={initialOptions} options={options} value={emailMarkup}
91+
className='email-editor' {...other} />
5992
</div>
6093

6194
<div className='right'>

frontend/src/app/pages/email-templates/editor/EmailHtmlTextEditor.tsx

-91
This file was deleted.

frontend/src/app/pages/email-templates/editor/EmailTextEditor.tsx

+14-5
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
* Copyright (c) Sebastian Stehle. All rights reserved.
66
*/
77

8+
import * as React from 'react';
89
import Split from 'react-split';
910
import { Alert } from 'reactstrap';
1011
import { CodeEditor, CodeEditorProps } from '@app/framework';
11-
import { usePreview } from './helpers';
12+
import { useErrors, usePreview } from './helpers';
1213
import 'codemirror/mode/django/django';
1314

1415
export interface EmailTextEditorProps extends CodeEditorProps {
1516
// The app name.
1617
appId: string;
1718
}
1819

19-
const OPTIONS = {
20+
const INITIAL_OPTIONS = {
2021
mode: 'django',
2122
};
2223

@@ -30,14 +31,22 @@ export const EmailTextEditor = (props: EmailTextEditorProps) => {
3031
const emailMarkup = value || '';
3132
const emailPreview = usePreview(appId, emailMarkup, 'Text');
3233

33-
const error = emailPreview.rendering.errors?.find(x => !x.lineNumber || x.lineNumber < 0);
34+
const { lint, error } = useErrors(emailPreview.rendering);
35+
36+
const options = React.useMemo(() => {
37+
const result: CodeEditorProps['options'] = {
38+
lint,
39+
};
40+
41+
return result;
42+
}, [lint]);
3443

3544
return (
3645
<div className='email-editor white'>
3746
<Split direction='horizontal'>
3847
<div className='left'>
39-
<CodeEditor value={value} {...other}
40-
initialOptions={OPTIONS} />
48+
<CodeEditor initialOptions={INITIAL_OPTIONS} options={options} value={emailMarkup}
49+
className='email-editor' {...other} />
4150
</div>
4251

4352
<div className='right'>

frontend/src/app/pages/email-templates/editor/helpers.ts

+28-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
/* eslint-disable quote-props */
99

1010
import * as CodeMirror from 'codemirror';
11+
import * as React from 'react';
1112
import { useDebounce, useSimpleQuery } from '@app/framework';
1213
import { Clients, EmailPreviewDto, EmailPreviewType } from '@app/service';
1314

@@ -19,14 +20,14 @@ export function usePreview(appId: string, sourceTemplate: string, type: EmailPre
1920
const template = useDebounce(sourceTemplate, 500);
2021

2122
const query = useSimpleQuery<MarkupResponse>({
22-
queryKey: [template, type],
23-
queryFn: async () => {
23+
queryKey: [appId, template, type],
24+
queryFn: async abort => {
2425
if (!template) {
2526
return { rendering: {} };
2627
}
2728

2829
try {
29-
const rendering = await Clients.EmailTemplates.postPreview(appId, { template, type });
30+
const rendering = await Clients.EmailTemplates.postPreview(appId, { template, type }, abort);
3031

3132
return { rendering, template };
3233
} catch (ex: any) {
@@ -45,6 +46,30 @@ export function usePreview(appId: string, sourceTemplate: string, type: EmailPre
4546
return query.value;
4647
}
4748

49+
export function useErrors(rendering?: EmailPreviewDto) {
50+
const errors = rendering?.errors;
51+
52+
return React.useMemo(() => {
53+
const error = errors?.find(x => !x.lineNumber || x.lineNumber < 0);
54+
55+
const lint = {
56+
getAnnotations: () => {
57+
if (!errors) {
58+
return [];
59+
}
60+
61+
return errors.filter(x => x.lineNumber >= 0).map(({ message, lineNumber }) => {
62+
const from = CodeMirror.Pos(lineNumber! - 1, 1);
63+
64+
return { message, severity: 'error', from, to: from };
65+
});
66+
},
67+
};
68+
69+
return { error, lint };
70+
}, [errors]);
71+
}
72+
4873
export function completeAfter(editor: CodeMirror.Editor, predicate?: (cursor: CodeMirror.Position) => boolean) {
4974
const cursor = editor.getCursor();
5075

frontend/src/app/pages/messaging-templates/MessagingTemplatePage.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ export const MessagingTemplatePage = () => {
3939
const [language, setLanguage] = React.useState(appLanguages[0]);
4040

4141
const properties = useSimpleQuery<TemplatePropertyDto[]>({
42-
queryKey: [],
43-
queryFn: async () => {
44-
const result = await Clients.MessagingTemplates.getProperties(appId);
42+
queryKey: [appId],
43+
queryFn: async abort => {
44+
const result = await Clients.MessagingTemplates.getProperties(appId, abort);
4545

4646
return result.items;
4747
},

frontend/src/app/pages/sms-templates/SmsTemplatePage.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ export const SmsTemplatePage = () => {
3939
const [language, setLanguage] = React.useState(appLanguages[0]);
4040

4141
const properties = useSimpleQuery<TemplatePropertyDto[]>({
42-
queryKey: [],
42+
queryKey: [appId],
4343
queryFn: async () => {
44-
const result = await Clients.SmsTemplates.getProperties(appId);
44+
const result = await Clients.SmsTemplates.getProperties(appId, abort);
4545

4646
return result.items;
4747
},

0 commit comments

Comments
 (0)