#147 - Error boundary added for metadata fields

This commit is contained in:
Elio Struyf
2021-10-13 09:05:32 +02:00
parent 5cc83526ad
commit 2fe7c524e7
4 changed files with 139 additions and 59 deletions
+1
View File
@@ -6,6 +6,7 @@
- [#141](https://github.com/estruyf/vscode-front-matter/issues/141): Allow content creation for page bundles or single files
- [#145](https://github.com/estruyf/vscode-front-matter/issues/145): Moved folder registration settings to `frontmatter.json` file
- [#147](https://github.com/estruyf/vscode-front-matter/issues/147): Error boundary added for metadata fields
### 🐞 Fixes
+19
View File
@@ -437,6 +437,25 @@ input:checked + .field__toggle__slider:before {
margin-right: .5rem;
}
.metadata_field__error {
color: var(--vscode-errorForeground);
display: flex;
justify-content: space-between;
align-items: center;
}
.metadata_field__error button {
color: var(--vscode-button-secondaryForeground);
background-color: var(--vscode-button-secondaryBackground);
padding-left: 1rem;
padding-right: 1rem;
width: auto;
}
.metadata_field__error button:hover {
background-color: var(--vscode-button-secondaryHoverBackground);
}
.metadata_field__input, .metadata_field__input:focus,
.metadata_field__textarea, .metadata_field__textarea:focus {
outline: none;
@@ -0,0 +1,49 @@
import * as React from 'react';
import * as Sentry from "@sentry/react";
import { VsLabel } from '../VscodeComponents';
export interface IFieldBoundaryProps {
fieldName: string;
}
export interface IFieldBoundaryState {
hasError: boolean;
}
export default class FieldBoundary extends React.Component<IFieldBoundaryProps, IFieldBoundaryState> {
constructor(props: IFieldBoundaryProps) {
super(props);
this.state = { hasError: false };
}
public static getDerivedStateFromError(error: any) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
public componentDidCatch(error: any, errorInfo: any) {
Sentry.captureMessage(`Field boundary: ${error?.message || error}`);
}
public render(): React.ReactElement<IFieldBoundaryProps> {
if (this.state.hasError) {
return (
<div className={`metadata_field`}>
<VsLabel>
<div className={`metadata_field__label`}>
<span style={{ lineHeight: "16px"}}>{this.props.fieldName}</span>
</div>
</VsLabel>
<div className={`metadata_field__error`}>
<span>Error loading field</span>
<button onClick={() => this.setState({ hasError: false })}>Retry</button>
</div>
</div>
);
}
return this.props.children as any;
}
}
+70 -59
View File
@@ -17,6 +17,7 @@ import { NumberField } from './Fields/NumberField';
import { ChoiceField } from './Fields/ChoiceField';
import useContentType from '../../hooks/useContentType';
import { DateHelper } from '../../helpers/DateHelper';
import FieldBoundary from './ErrorBoundary/FieldBoundary';
export interface IMetadataProps {
settings: PanelSettings | undefined;
@@ -62,20 +63,23 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
const dateValue = metadata[field.name] ? getDate(metadata[field.name] as string) : null;
return (
<DateTimeField
key={field.name}
label={field.title || field.name}
date={dateValue}
format={settings?.date?.format}
onChange={(date => sendUpdate(field.name, date))} />
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<DateTimeField
label={field.title || field.name}
date={dateValue}
format={settings?.date?.format}
onChange={(date => sendUpdate(field.name, date))} />
</FieldBoundary>
);
} else if (field.type === 'boolean') {
return (
<Toggle
key={field.name}
label={field.title || field.name}
checked={!!metadata[field.name] as any}
onChanged={(checked) => sendUpdate(field.name, checked)} />
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<Toggle
key={field.name}
label={field.title || field.name}
checked={!!metadata[field.name] as any}
onChanged={(checked) => sendUpdate(field.name, checked)} />
</FieldBoundary>
);
} else if (field.type === 'string') {
const textValue = metadata[field.name];
@@ -88,14 +92,15 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
}
return (
<TextField
key={field.name}
label={field.title || field.name}
singleLine={field.single}
limit={limit}
rows={3}
onChange={(value) => sendUpdate(field.name, value)}
value={textValue as string || null} />
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<TextField
label={field.title || field.name}
singleLine={field.single}
limit={limit}
rows={3}
onChange={(value) => sendUpdate(field.name, value)}
value={textValue as string || null} />
</FieldBoundary>
);
} else if (field.type === 'number') {
const fieldValue = metadata[field.name];
@@ -105,61 +110,67 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
}
return (
<NumberField
key={field.name}
label={field.title || field.name}
onChange={(value) => sendUpdate(field.name, value)}
value={nrValue} />
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<NumberField
key={field.name}
label={field.title || field.name}
onChange={(value) => sendUpdate(field.name, value)}
value={nrValue} />
</FieldBoundary>
);
} else if (field.type === 'image') {
return (
<PreviewImageField
key={field.name}
label={field.title || field.name}
fieldName={field.name}
filePath={metadata.filePath as string}
value={metadata[field.name] as PreviewImageValue | PreviewImageValue[] | null}
multiple={field.multiple}
onChange={(value => sendUpdate(field.name, value))} />
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<PreviewImageField
label={field.title || field.name}
fieldName={field.name}
filePath={metadata.filePath as string}
value={metadata[field.name] as PreviewImageValue | PreviewImageValue[] | null}
multiple={field.multiple}
onChange={(value => sendUpdate(field.name, value))} />
</FieldBoundary>
);
} else if (field.type === 'choice') {
const choices = field.choices || [];
const choiceValue = metadata[field.name];
return (
<ChoiceField
key={field.name}
label={field.title || field.name}
selected={choiceValue as string}
choices={choices}
multiSelect={field.multiple}
onChange={(value => sendUpdate(field.name, value))} />
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<ChoiceField
label={field.title || field.name}
selected={choiceValue as string}
choices={choices}
multiSelect={field.multiple}
onChange={(value => sendUpdate(field.name, value))} />
</FieldBoundary>
);
} else if (field.type === 'tags') {
return (
<TagPicker
key={field.name}
type={TagType.tags}
label={field.title || field.name}
icon={<TagIcon />}
crntSelected={metadata[field.name] as string[] || []}
options={settings?.tags || []}
freeform={settings.freeform}
focussed={focusElm === TagType.tags}
unsetFocus={unsetFocus} />
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<TagPicker
type={TagType.tags}
label={field.title || field.name}
icon={<TagIcon />}
crntSelected={metadata[field.name] as string[] || []}
options={settings?.tags || []}
freeform={settings.freeform}
focussed={focusElm === TagType.tags}
unsetFocus={unsetFocus} />
</FieldBoundary>
);
} else if (field.type === 'categories') {
return (
<TagPicker
key={field.name}
type={TagType.categories}
label={field.title || field.name}
icon={<ListUnorderedIcon />}
crntSelected={metadata.categories as string[] || []}
options={settings.categories}
freeform={settings.freeform}
focussed={focusElm === TagType.categories}
unsetFocus={unsetFocus} />
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<TagPicker
type={TagType.categories}
label={field.title || field.name}
icon={<ListUnorderedIcon />}
crntSelected={metadata.categories as string[] || []}
options={settings.categories}
freeform={settings.freeform}
focussed={focusElm === TagType.categories}
unsetFocus={unsetFocus} />
</FieldBoundary>
);
} else {
return null;