Skip to content

Commit

Permalink
feat: Message when field values are invalid for field or missing fiel…
Browse files Browse the repository at this point in the history
…d. (#218)

When the editor is trying to show a field but the value is not able to be used for the field it will show a message that the value is not editable in the editor UI.

When a field type is not recognized and `Unknown` field will be used that shows a message that the field type needs to be updated.

fixes #154
fixes #94
  • Loading branch information
Zoramite committed Aug 11, 2021
1 parent 4e89819 commit cc0cdf8
Show file tree
Hide file tree
Showing 16 changed files with 219 additions and 32 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@
"ts-node": "^10.2.0",
"typedoc": "^0.21.5",
"typescript": "^4.3.5",
"webpack": "^5.49.0",
"webpack": "^5.50.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@blinkk/selective-edit": "^2.2.1",
"@blinkk/selective-edit": "^2.3.0",
"@toast-ui/editor": "^3.0.2",
"bent": "^7.3.12",
"codemirror": "^5.62.2",
Expand Down
2 changes: 2 additions & 0 deletions src/sass/editor.sass
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,13 @@ a,
@import './ui/button'
@import './ui/error'
@import './ui/grid'
@import './ui/info'
@import './ui/list'
@import './ui/loading'
@import './ui/modal'
@import './ui/toast'
@import './ui/tooltip'
@import './ui/warning'

@import './parts/content'
@import './parts/dashboard'
Expand Down
1 change: 1 addition & 0 deletions src/sass/selective/_field.sass
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@
@import './field/text'
@import './field/textarea'
@import './field/time'
@import './field/unknown'
@import './field/variant'

// Generic/shared fields.
Expand Down
22 changes: 22 additions & 0 deletions src/sass/selective/_selective.sass
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
.selective
margin: $le-space-medium

.selective__error,
.selective__info,
.selective__warning
align-items: center
column-gap: $le-space-medium
line-height: $le-line-height-paragraph
display: flex
flex-flow: row
padding: $le-space-medium

.selective__error
border: $le-border-size-medium dashed var(--color-error)
color: var(--color-error)

.selective__info
border: $le-border-size-medium dashed var(--color-info)
color: var(--color-info)

.selective__warning
border: $le-border-size-medium dashed var(--color-warning)
color: var(--color-warning)

@import './field'
@import './fields'
@import './mixins'
2 changes: 2 additions & 0 deletions src/sass/selective/field/_unknown.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.selective__field__type__unknown
line-height: $le-line-height-paragraph
8 changes: 8 additions & 0 deletions src/sass/ui/_info.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.le__info
align-items: center
border: $le-border-size-medium dashed var(--color-info)
color: var(--color-info)
column-gap: $le-space-medium
display: flex
flex-flow: row
padding: $le-space-medium
8 changes: 8 additions & 0 deletions src/sass/ui/_warning.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.le__warning
align-items: center
border: $le-border-size-medium dashed var(--color-warning)
color: var(--color-warning)
column-gap: $le-space-medium
display: flex
flex-flow: row
padding: $le-space-medium
2 changes: 1 addition & 1 deletion src/ts/editor/field/html.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
DataType,
DeepObject,
Field,
FieldConfig,
Expand All @@ -10,7 +11,6 @@ import {
} from '@blinkk/selective-edit';
import {EVENT_RENDER_COMPLETE} from '../events';
import {LiveEditorGlobalConfig} from '../editor';
import {MediaOptions} from '../api';
import Quill from 'quill';
import {base64toFile} from '../../utility/base64';

Expand Down
31 changes: 31 additions & 0 deletions src/ts/editor/field/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,37 @@ export class MediaField
return super.isClean && this.group.isClean;
}

/**
* Check if the data format is invalid for what the field expects to edit.
*/
get isDataFormatValid(): boolean {
if (this.originalValue === undefined || this.originalValue === null) {
return true;
}

if (!DataType.isObject(this.originalValue)) {
return false;
}

// Label needs to be a string.
if (
this.originalValue.label &&
!DataType.isString(this.originalValue.label)
) {
return false;
}

// Path needs to be a string.
if (
this.originalValue.path &&
!DataType.isString(this.originalValue.path)
) {
return false;
}

return true;
}

get isSimple(): boolean {
// Media field has multiple inputs and is considered complex.
return false;
Expand Down
12 changes: 12 additions & 0 deletions src/ts/editor/field/mediaList.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Base,
DataType,
DeepObject,
DroppableMixin,
Field,
Expand Down Expand Up @@ -364,6 +365,17 @@ export class MediaListField
return true;
}

/**
* Check if the data format is invalid for what the field expects to edit.
*/
get isDataFormatValid(): boolean {
if (this.originalValue === undefined || this.originalValue === null) {
return true;
}

return DataType.isArray(this.originalValue);
}

get isValid(): boolean {
// If there are no items, nothing has changed.
if (this.items === null) {
Expand Down
58 changes: 58 additions & 0 deletions src/ts/editor/field/unknown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
DeepObject,
Field,
FieldConfig,
SelectiveEditor,
TemplateResult,
Types,
html,
} from '@blinkk/selective-edit';
import {LiveEditorGlobalConfig} from '../editor';
import {templateInfo} from '../template';

export type UnknownFieldConfig = FieldConfig;

export class UnknownField extends Field {
config: UnknownFieldConfig;

constructor(
types: Types,
config: UnknownFieldConfig,
globalConfig: LiveEditorGlobalConfig,
fieldType = 'unknown'
) {
super(types, config, globalConfig, fieldType);
this.config = config;
}

/**
* Template for determining how to render the field.
*
* @param editor Selective editor used to render the template.
* @param data Data provided to render the template.
*/
template(editor: SelectiveEditor, data: DeepObject): TemplateResult {
return this.templateWrapper(editor, data);
}

/**
* Template for rendering the field structure.
*
* Used for controlling the order that parts of the field are rendered.
*
* @param editor Selective editor used to render the template.
* @param data Data provided to render the template.
*/
templateInputStructure(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
editor: SelectiveEditor,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
data: DeepObject
): TemplateResult {
return html`<div class="selective__field__input__structure">
${templateInfo(html`Unable to display this field that is using the
<code>${this.config.type}</code> field type. Please contact the
developer to update the editor configuration to use a valid field type.`)}
</div>`;
}
}
28 changes: 28 additions & 0 deletions src/ts/editor/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,34 @@ export interface TemplateOptions {
size?: 'small' | 'medium' | 'large';
}

export function templateError(message?: TemplateResult): TemplateResult {
return templateMessage('error', message);
}

export function templateInfo(message?: TemplateResult): TemplateResult {
return templateMessage('info', message);
}

export function templateWarning(message?: TemplateResult): TemplateResult {
return templateMessage('warning', message);
}

export function templateMessage(
messageType: 'info' | 'warning' | 'error',
message?: TemplateResult,
icon?: string
): TemplateResult {
const classes: Record<string, boolean> = {};
classes[`le__${messageType}`] = true;

return html`<div class=${classMap(classes)}>
<div class="le__${messageType}__icon">
<span class="material-icons">${icon || messageType}</span>
</div>
<div class="le__${messageType}__message">${message}</div>
</div>`;
}

export function templateLoading(
options?: TemplateOptions,
message?: TemplateResult
Expand Down
4 changes: 4 additions & 0 deletions src/ts/editor/ui/parts/content/sectionFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {DeepObject, TemplateResult} from '@blinkk/selective-edit';
import {EVENT_SAVE} from '../../../events';
import {EditorFileData} from '../../../api';
import {StatePromiseKeys} from '../../../state';
import {UnknownField} from '../../../field/unknown';
import merge from 'lodash.merge';

export class FieldsPart extends ContentSectionPart {
Expand All @@ -29,6 +30,9 @@ export class FieldsPart extends ContentSectionPart {
this.handleAction(evt);
}
});

// Custom field for unknown fields.
this.selective.types.fields.DefaultCls = UnknownField as any;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
6 changes: 3 additions & 3 deletions src/ts/example/exampleApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ const fullFiles: Record<string, EditorFileData> = {
// Html example.
{
type: 'exampleField',
key: 'html-small',
key: 'htmlSmall',
docUrls: [
{
label: 'Config interface',
Expand All @@ -227,7 +227,7 @@ const fullFiles: Record<string, EditorFileData> = {
} as ExampleFieldConfig,
{
type: 'exampleField',
key: 'html-medium',
key: 'htmlMedium',
docUrls: [
{
label: 'Config interface',
Expand All @@ -247,7 +247,7 @@ const fullFiles: Record<string, EditorFileData> = {
} as ExampleFieldConfig,
{
type: 'exampleField',
key: 'html-large',
key: 'htmlLarge',
docUrls: [
{
label: 'Config interface',
Expand Down
11 changes: 11 additions & 0 deletions src/ts/projectType/generic/field/constructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ export class ConstructorField extends Field implements ConstructorComponent {
this.render();
}

/**
* Check if the data format is invalid for what the field expects to edit.
*/
get isDataFormatValid(): boolean {
if (this.originalValue === undefined || this.originalValue === null) {
return true;
}

return DataType.isObject(this.originalValue);
}

setCurrentValue(value: any) {
if (DataType.isString(value) && value.trim() === '') {
this.currentValue = null;
Expand Down

0 comments on commit cc0cdf8

Please sign in to comment.