introduces Row, modifies buildSchema to support fields that need to modify top-level schemas
This commit is contained in:
@@ -54,13 +54,13 @@ const ColumnSelector = (props) => {
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{fields && fields.map((field) => {
|
||||
{fields && fields.map((field, i) => {
|
||||
const isEnabled = columns.find(column => column === field.name);
|
||||
return (
|
||||
<Pill
|
||||
onClick={() => dispatchColumns({ payload: field.name, type: isEnabled ? 'disable' : 'enable' })}
|
||||
alignIcon="left"
|
||||
key={field.name}
|
||||
key={field.name || i}
|
||||
icon={isEnabled ? <X /> : <Plus />}
|
||||
pillStyle={isEnabled ? 'dark' : undefined}
|
||||
className={`${baseClass}__active-column`}
|
||||
|
||||
@@ -10,7 +10,6 @@ const RenderFields = ({
|
||||
return (
|
||||
<>
|
||||
{fieldSchema.map((field, i) => {
|
||||
const { defaultValue } = field;
|
||||
if (field?.hidden !== 'api' && field?.hidden !== true) {
|
||||
if ((filter && typeof filter === 'function' && filter(field)) || !filter) {
|
||||
let FieldComponent = field?.hidden === 'admin' ? fieldTypes.hidden : fieldTypes[field.type];
|
||||
@@ -19,14 +18,22 @@ const RenderFields = ({
|
||||
FieldComponent = customComponents[field.name].field;
|
||||
}
|
||||
|
||||
let { defaultValue } = field;
|
||||
|
||||
if (!field.name) {
|
||||
defaultValue = initialData;
|
||||
} else if (initialData[field.name]) {
|
||||
defaultValue = initialData[field.name];
|
||||
}
|
||||
|
||||
if (FieldComponent) {
|
||||
return (
|
||||
<FieldComponent
|
||||
fieldTypes={fieldTypes}
|
||||
key={field.name}
|
||||
key={field.name || `field-${i}`}
|
||||
{...field}
|
||||
validate={field.validate ? value => field.validate(value, field) : undefined}
|
||||
defaultValue={initialData[field.name] || defaultValue}
|
||||
defaultValue={defaultValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,14 +39,12 @@ const Checkbox = (props) => {
|
||||
showError && 'error',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const fieldWidth = width ? `${width}%` : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width: fieldWidth,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
@@ -72,7 +70,7 @@ Checkbox.defaultProps = {
|
||||
defaultValue: false,
|
||||
validate: null,
|
||||
errorMessage: defaultError,
|
||||
width: 100,
|
||||
width: undefined,
|
||||
style: {},
|
||||
};
|
||||
|
||||
@@ -82,7 +80,7 @@ Checkbox.propTypes = {
|
||||
defaultValue: PropTypes.bool,
|
||||
validate: PropTypes.func,
|
||||
errorMessage: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
label: PropTypes.string,
|
||||
};
|
||||
|
||||
@@ -44,14 +44,12 @@ const DateTime = (props) => {
|
||||
formProcessing && 'processing',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const fieldWidth = width ? `${width}%` : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width: fieldWidth,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
@@ -80,7 +78,7 @@ DateTime.defaultProps = {
|
||||
defaultValue: null,
|
||||
validate: null,
|
||||
errorMessage: defaultError,
|
||||
width: 100,
|
||||
width: undefined,
|
||||
style: {},
|
||||
};
|
||||
|
||||
@@ -91,7 +89,7 @@ DateTime.propTypes = {
|
||||
defaultValue: PropTypes.string,
|
||||
validate: PropTypes.func,
|
||||
errorMessage: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
};
|
||||
|
||||
|
||||
@@ -50,14 +50,12 @@ const Email = (props) => {
|
||||
showError && 'error',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const fieldWidth = width ? `${width}%` : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width: fieldWidth,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
@@ -90,7 +88,7 @@ Email.defaultProps = {
|
||||
placeholder: undefined,
|
||||
validate: defaultValidate,
|
||||
errorMessage: defaultError,
|
||||
width: 100,
|
||||
width: undefined,
|
||||
style: {},
|
||||
autoComplete: undefined,
|
||||
};
|
||||
@@ -102,7 +100,7 @@ Email.propTypes = {
|
||||
defaultValue: PropTypes.string,
|
||||
validate: PropTypes.func,
|
||||
errorMessage: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
label: PropTypes.string,
|
||||
autoComplete: PropTypes.string,
|
||||
|
||||
@@ -17,8 +17,8 @@ const Group = (props) => {
|
||||
fieldSchema={fields.map((subField) => {
|
||||
return {
|
||||
...subField,
|
||||
name: `${name}.${subField.name}`,
|
||||
defaultValue: defaultValue[subField.name],
|
||||
name: `${name}${subField.name ? `.${subField.name}` : ''}`,
|
||||
defaultValue: subField.name ? defaultValue[subField.name] : defaultValue,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -41,14 +41,12 @@ const NumberField = (props) => {
|
||||
showError && 'error',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const fieldWidth = width ? `${width}%` : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width: fieldWidth,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
@@ -80,7 +78,7 @@ NumberField.defaultProps = {
|
||||
placeholder: undefined,
|
||||
validate: defaultValidate,
|
||||
errorMessage: defaultError,
|
||||
width: 100,
|
||||
width: undefined,
|
||||
style: {},
|
||||
};
|
||||
|
||||
@@ -91,7 +89,7 @@ NumberField.propTypes = {
|
||||
defaultValue: PropTypes.number,
|
||||
validate: PropTypes.func,
|
||||
errorMessage: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
label: PropTypes.string,
|
||||
};
|
||||
|
||||
@@ -34,8 +34,6 @@ const Password = (props) => {
|
||||
validate,
|
||||
});
|
||||
|
||||
const fieldWidth = width ? `${width}%` : null;
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
'password',
|
||||
@@ -47,7 +45,7 @@ const Password = (props) => {
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width: fieldWidth,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
@@ -77,7 +75,7 @@ Password.defaultProps = {
|
||||
defaultValue: null,
|
||||
validate: defaultValidate,
|
||||
errorMessage: defaultError,
|
||||
width: 100,
|
||||
width: undefined,
|
||||
style: {},
|
||||
};
|
||||
|
||||
@@ -86,7 +84,7 @@ Password.propTypes = {
|
||||
required: PropTypes.bool,
|
||||
defaultValue: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
label: PropTypes.string.isRequired,
|
||||
validate: PropTypes.func,
|
||||
|
||||
@@ -210,9 +210,6 @@ class Relationship extends Component {
|
||||
showError && 'error',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
// eslint-disable-next-line prefer-template
|
||||
const fieldWidth = width ? width + '%' : null;
|
||||
|
||||
const valueToRender = this.findValueInOptions(options, value);
|
||||
|
||||
// ///////////////////////////////////////////
|
||||
@@ -224,7 +221,7 @@ class Relationship extends Component {
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width: fieldWidth,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
@@ -258,7 +255,7 @@ Relationship.defaultProps = {
|
||||
required: false,
|
||||
errorMessage: defaultError,
|
||||
hasMany: false,
|
||||
width: 100,
|
||||
width: undefined,
|
||||
showError: false,
|
||||
value: null,
|
||||
formProcessing: false,
|
||||
@@ -282,7 +279,7 @@ Relationship.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
formProcessing: PropTypes.bool,
|
||||
width: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
hasMany: PropTypes.bool,
|
||||
onFieldChange: PropTypes.func.isRequired,
|
||||
hasMultipleRelations: PropTypes.bool.isRequired,
|
||||
|
||||
43
src/client/components/forms/field-types/Row/index.js
Normal file
43
src/client/components/forms/field-types/Row/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import RenderFields from '../../RenderFields';
|
||||
import withCondition from '../../withCondition';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const Row = (props) => {
|
||||
const {
|
||||
fields, fieldTypes, name, defaultValue,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="field-type row">
|
||||
<RenderFields
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields.map((field) => {
|
||||
return {
|
||||
...field,
|
||||
name: `${name ? `${name}.` : ''}${field.name}`,
|
||||
defaultValue: defaultValue ? defaultValue[field.name] : null,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Row.defaultProps = {
|
||||
name: '',
|
||||
defaultValue: null,
|
||||
};
|
||||
|
||||
Row.propTypes = {
|
||||
fields: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
).isRequired,
|
||||
fieldTypes: PropTypes.shape({}).isRequired,
|
||||
name: PropTypes.string,
|
||||
defaultValue: PropTypes.shape({}),
|
||||
};
|
||||
|
||||
export default withCondition(Row);
|
||||
13
src/client/components/forms/field-types/Row/index.scss
Normal file
13
src/client/components/forms/field-types/Row/index.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.field-type.row {
|
||||
display: flex;
|
||||
margin-left: - base(.5);
|
||||
margin-right: - base(.5);
|
||||
width: calc(100% + #{$baseline});
|
||||
|
||||
> * {
|
||||
margin-left: base(.5);
|
||||
margin-right: base(.5);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ const formatFormValue = (value) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value.value) {
|
||||
if (typeof value === 'object' && value !== null && value.value) {
|
||||
return value.value;
|
||||
}
|
||||
|
||||
@@ -85,14 +85,12 @@ const Select = (props) => {
|
||||
showError && 'error',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const fieldWidth = width ? `${width}%` : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width: fieldWidth,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
@@ -124,7 +122,7 @@ Select.defaultProps = {
|
||||
validate: defaultValidate,
|
||||
defaultValue: null,
|
||||
hasMany: false,
|
||||
width: 100,
|
||||
width: undefined,
|
||||
};
|
||||
|
||||
Select.propTypes = {
|
||||
@@ -138,7 +136,7 @@ Select.propTypes = {
|
||||
]),
|
||||
validate: PropTypes.func,
|
||||
name: PropTypes.string.isRequired,
|
||||
width: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
options: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(
|
||||
PropTypes.string,
|
||||
|
||||
@@ -41,14 +41,12 @@ const Text = (props) => {
|
||||
showError && 'error',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const fieldWidth = width ? `${width}%` : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width: fieldWidth,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
@@ -80,7 +78,7 @@ Text.defaultProps = {
|
||||
placeholder: undefined,
|
||||
validate: defaultValidate,
|
||||
errorMessage: defaultError,
|
||||
width: 100,
|
||||
width: undefined,
|
||||
style: {},
|
||||
};
|
||||
|
||||
@@ -91,7 +89,7 @@ Text.propTypes = {
|
||||
defaultValue: PropTypes.string,
|
||||
validate: PropTypes.func,
|
||||
errorMessage: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
label: PropTypes.string,
|
||||
};
|
||||
|
||||
@@ -41,14 +41,12 @@ const Textarea = (props) => {
|
||||
showError && 'error',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const fieldWidth = width ? `${width}%` : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width: fieldWidth,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
@@ -78,7 +76,7 @@ Textarea.defaultProps = {
|
||||
defaultValue: null,
|
||||
validate: defaultValidate,
|
||||
errorMessage: defaultError,
|
||||
width: 100,
|
||||
width: undefined,
|
||||
style: {},
|
||||
placeholder: null,
|
||||
};
|
||||
@@ -89,7 +87,7 @@ Textarea.propTypes = {
|
||||
defaultValue: PropTypes.string,
|
||||
validate: PropTypes.func,
|
||||
errorMessage: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
label: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
|
||||
@@ -13,3 +13,4 @@ export { default as checkbox } from './Checkbox';
|
||||
export { default as flexible } from './Flexible';
|
||||
export { default as group } from './Group';
|
||||
export { default as repeater } from './Repeater';
|
||||
export { default as row } from './Row';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useContext, useCallback, useEffect } from 'react';
|
||||
import FormContext from '../Form/Context';
|
||||
import { useLocale } from '../../utilities/Locale';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -12,6 +13,7 @@ const useFieldType = (options) => {
|
||||
validate,
|
||||
} = options;
|
||||
|
||||
const locale = useLocale();
|
||||
const formContext = useContext(FormContext);
|
||||
const { dispatchFields, submitted, processing } = formContext;
|
||||
let mountValue = formContext.fields[name]?.value;
|
||||
@@ -33,6 +35,10 @@ const useFieldType = (options) => {
|
||||
sendField(mountValue);
|
||||
}, [sendField, mountValue]);
|
||||
|
||||
useEffect(() => {
|
||||
sendField(null);
|
||||
}, [locale, sendField]);
|
||||
|
||||
// Remove field from state on "unmount"
|
||||
useEffect(() => {
|
||||
return () => dispatchFields({ name, type: 'REMOVE' });
|
||||
|
||||
@@ -41,11 +41,12 @@ const withCondition = (Field) => {
|
||||
|
||||
WithCondition.defaultProps = {
|
||||
condition: null,
|
||||
name: '',
|
||||
};
|
||||
|
||||
WithCondition.propTypes = {
|
||||
condition: PropTypes.func,
|
||||
name: PropTypes.string.isRequired,
|
||||
name: PropTypes.string,
|
||||
};
|
||||
|
||||
return WithCondition;
|
||||
|
||||
@@ -8,10 +8,15 @@
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__header {
|
||||
h1 {
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
const { Schema } = require('mongoose');
|
||||
|
||||
const formatBaseSchema = (field) => {
|
||||
return {
|
||||
hide: field.hidden === 'api' || field.hidden === true,
|
||||
localized: field.localized || false,
|
||||
unique: field.unique || false,
|
||||
required: (field.required && !field.localized && !field.hidden && !field.condition) || false,
|
||||
default: field.defaultValue || undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const fieldToSchemaMap = {
|
||||
number: (field) => {
|
||||
return { ...formatBaseSchema(field), type: Number };
|
||||
},
|
||||
text: (field) => {
|
||||
return { ...formatBaseSchema(field), type: String };
|
||||
},
|
||||
email: (field) => {
|
||||
return { ...formatBaseSchema(field), type: String };
|
||||
},
|
||||
textarea: (field) => {
|
||||
return { ...formatBaseSchema(field), type: String };
|
||||
},
|
||||
wysiwyg: (field) => {
|
||||
return { ...formatBaseSchema(field), type: String };
|
||||
},
|
||||
code: (field) => {
|
||||
return { ...formatBaseSchema(field), type: String };
|
||||
},
|
||||
checkbox: (field) => {
|
||||
return { ...formatBaseSchema(field), type: Boolean };
|
||||
},
|
||||
date: (field) => {
|
||||
return {
|
||||
...formatBaseSchema(field),
|
||||
type: Date,
|
||||
};
|
||||
},
|
||||
upload: (field) => {
|
||||
const schema = {
|
||||
...formatBaseSchema(field),
|
||||
type: Schema.Types.ObjectId,
|
||||
autopopulate: true,
|
||||
ref: field.type,
|
||||
};
|
||||
return schema;
|
||||
},
|
||||
relationship: (field) => {
|
||||
let schema = {};
|
||||
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
schema.value = {
|
||||
type: Schema.Types.ObjectId,
|
||||
autopopulate: true,
|
||||
refPath: `${field.name}${field.localized ? '.{{LOCALE}}' : ''}.relationTo`,
|
||||
};
|
||||
schema.relationTo = { type: String, enum: field.relationTo };
|
||||
} else {
|
||||
schema = {
|
||||
...formatBaseSchema(field),
|
||||
};
|
||||
|
||||
schema.type = Schema.Types.ObjectId;
|
||||
schema.autopopulate = true;
|
||||
schema.ref = field.relationTo;
|
||||
}
|
||||
|
||||
if (field.hasMany) {
|
||||
return {
|
||||
type: [schema],
|
||||
localized: field.localized,
|
||||
};
|
||||
}
|
||||
|
||||
return schema;
|
||||
},
|
||||
repeater: (field) => {
|
||||
const schema = {};
|
||||
|
||||
field.fields.forEach((subField) => {
|
||||
schema[subField.name] = fieldToSchemaMap[subField.type](subField);
|
||||
});
|
||||
return [schema];
|
||||
},
|
||||
group: (field) => {
|
||||
// Localization for groups not supported
|
||||
const schema = {};
|
||||
|
||||
field.fields.forEach((subField) => {
|
||||
schema[subField.name] = fieldToSchemaMap[subField.type](subField);
|
||||
});
|
||||
return schema;
|
||||
},
|
||||
select: (field) => {
|
||||
const schema = {
|
||||
...formatBaseSchema(field),
|
||||
type: String,
|
||||
enum: field.options.map((option) => {
|
||||
if (typeof option === 'object') return option.value;
|
||||
return option;
|
||||
}),
|
||||
};
|
||||
|
||||
return field.hasMany ? [schema] : schema;
|
||||
},
|
||||
flexible: (field) => {
|
||||
const flexibleSchema = new Schema({ blockName: String }, { discriminatorKey: 'blockType', _id: false });
|
||||
|
||||
return {
|
||||
type: [flexibleSchema],
|
||||
localized: field.localized || false,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = fieldToSchemaMap;
|
||||
@@ -14,7 +14,7 @@ const buildModel = (config) => {
|
||||
const Globals = mongoose.model('globals', globalsSchema);
|
||||
|
||||
Object.values(config.globals).forEach((globalConfig) => {
|
||||
const globalSchema = buildSchema(globalConfig.fields, config);
|
||||
const globalSchema = buildSchema(globalConfig.fields);
|
||||
|
||||
globalSchema
|
||||
.plugin(localizationPlugin, config.localization)
|
||||
|
||||
@@ -100,6 +100,22 @@ function buildMutationInputType(name, fields, parentName) {
|
||||
return { type };
|
||||
},
|
||||
flexible: () => ({ type: GraphQLJSON }),
|
||||
row: (field) => {
|
||||
return field.fields.reduce((acc, rowField) => {
|
||||
const getFieldSchema = fieldToSchemaMap[rowField.type];
|
||||
|
||||
if (getFieldSchema) {
|
||||
const fieldSchema = getFieldSchema(rowField);
|
||||
|
||||
return [
|
||||
...acc,
|
||||
fieldSchema,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}, []);
|
||||
},
|
||||
};
|
||||
|
||||
const fieldTypes = fields.reduce((schema, field) => {
|
||||
@@ -108,6 +124,15 @@ function buildMutationInputType(name, fields, parentName) {
|
||||
if (getFieldSchema) {
|
||||
const fieldSchema = getFieldSchema(field);
|
||||
|
||||
if (Array.isArray(fieldSchema)) {
|
||||
return fieldSchema.reduce((acc, subField, i) => {
|
||||
return {
|
||||
...acc,
|
||||
[field.fields[i].name]: subField,
|
||||
};
|
||||
}, schema);
|
||||
}
|
||||
|
||||
return {
|
||||
...schema,
|
||||
[formatName(field.name)]: fieldSchema,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
const schemaBaseFields = {
|
||||
// TODO: What is status being used for? It is probable that people are going to try to add their own status field for their own purposes. Is there a safe way we can house payload level fields in the future to avoid collisions?
|
||||
status: String,
|
||||
publishedAt: Date,
|
||||
};
|
||||
|
||||
module.exports = schemaBaseFields;
|
||||
@@ -1,43 +1,198 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
const { Schema } = require('mongoose');
|
||||
const fieldToSchemaMap = require('../../fields/schemaMap');
|
||||
const baseFields = require('./baseFields');
|
||||
|
||||
const buildSchema = (configFields, config, options = {}, additionalBaseFields = {}) => {
|
||||
const fields = { ...baseFields, ...additionalBaseFields };
|
||||
const flexiblefields = [];
|
||||
const formatBaseSchema = (field) => {
|
||||
return {
|
||||
hide: field.hidden === 'api' || field.hidden === true,
|
||||
localized: field.localized || false,
|
||||
unique: field.unique || false,
|
||||
required: (field.required && !field.localized && !field.hidden && !field.condition) || false,
|
||||
default: field.defaultValue || undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const buildSchema = (configFields, options = {}) => {
|
||||
let fields = {};
|
||||
|
||||
configFields.forEach((field) => {
|
||||
const fieldSchema = fieldToSchemaMap[field.type];
|
||||
if (field.type === 'flexible') {
|
||||
flexiblefields.push(field);
|
||||
}
|
||||
|
||||
if (fieldSchema) {
|
||||
fields[field.name] = fieldSchema(field, config);
|
||||
fields = fieldSchema(field, fields);
|
||||
}
|
||||
});
|
||||
|
||||
const schema = new Schema(fields, options);
|
||||
|
||||
if (flexiblefields.length > 0) {
|
||||
flexiblefields.forEach((field) => {
|
||||
if (field.blocks && field.blocks.length > 0) {
|
||||
field.blocks.forEach((block) => {
|
||||
const blockSchemaFields = {};
|
||||
configFields.forEach((field) => {
|
||||
if (field.type === 'flexible' && field.blocks && field.blocks.length > 0) {
|
||||
field.blocks.forEach((block) => {
|
||||
let blockSchemaFields = {};
|
||||
|
||||
block.fields.forEach((blockField) => {
|
||||
const fieldSchema = fieldToSchemaMap[blockField.type];
|
||||
if (fieldSchema) blockSchemaFields[blockField.name] = fieldSchema(blockField, config);
|
||||
});
|
||||
|
||||
const blockSchema = new Schema(blockSchemaFields, { _id: false });
|
||||
schema.path(field.name).discriminator(block.slug, blockSchema);
|
||||
block.fields.forEach((blockField) => {
|
||||
const fieldSchema = fieldToSchemaMap[blockField.type];
|
||||
if (fieldSchema) {
|
||||
blockSchemaFields = fieldSchema(blockField, blockSchemaFields);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const blockSchema = new Schema(blockSchemaFields, { _id: false });
|
||||
schema.path(field.name).discriminator(block.slug, blockSchema);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return schema;
|
||||
};
|
||||
|
||||
const fieldToSchemaMap = {
|
||||
number: (field, fields) => {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: Number },
|
||||
};
|
||||
},
|
||||
text: (field, fields) => {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: String },
|
||||
};
|
||||
},
|
||||
email: (field, fields) => {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: String },
|
||||
};
|
||||
},
|
||||
textarea: (field, fields) => {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: String },
|
||||
};
|
||||
},
|
||||
wysiwyg: (field, fields) => {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: String },
|
||||
};
|
||||
},
|
||||
code: (field, fields) => {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: String },
|
||||
};
|
||||
},
|
||||
checkbox: (field, fields) => {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: Boolean },
|
||||
};
|
||||
},
|
||||
date: (field, fields) => {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: Date },
|
||||
};
|
||||
},
|
||||
upload: (field, fields) => {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: {
|
||||
...formatBaseSchema(field),
|
||||
type: Schema.Types.ObjectId,
|
||||
autopopulate: true,
|
||||
ref: field.type,
|
||||
},
|
||||
};
|
||||
},
|
||||
relationship: (field, fields) => {
|
||||
let schema = {};
|
||||
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
schema.value = {
|
||||
type: Schema.Types.ObjectId,
|
||||
autopopulate: true,
|
||||
refPath: `${field.name}${field.localized ? '.{{LOCALE}}' : ''}.relationTo`,
|
||||
};
|
||||
schema.relationTo = { type: String, enum: field.relationTo };
|
||||
} else {
|
||||
schema = {
|
||||
...formatBaseSchema(field),
|
||||
};
|
||||
|
||||
schema.type = Schema.Types.ObjectId;
|
||||
schema.autopopulate = true;
|
||||
schema.ref = field.relationTo;
|
||||
}
|
||||
|
||||
if (field.hasMany) {
|
||||
return {
|
||||
type: [schema],
|
||||
localized: field.localized,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schema,
|
||||
};
|
||||
},
|
||||
row: (field, fields) => {
|
||||
const newFields = { ...fields };
|
||||
|
||||
field.fields.forEach((rowField) => {
|
||||
const fieldSchemaMap = fieldToSchemaMap[rowField.type];
|
||||
|
||||
if (fieldSchemaMap) {
|
||||
const fieldSchema = fieldSchemaMap(rowField, fields);
|
||||
newFields[rowField.name] = fieldSchema[rowField.name];
|
||||
}
|
||||
});
|
||||
|
||||
return newFields;
|
||||
},
|
||||
repeater: (field, fields) => {
|
||||
const schema = buildSchema(field.fields);
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: [schema],
|
||||
};
|
||||
},
|
||||
group: (field, fields) => {
|
||||
const schema = buildSchema(field.fields, { _id: false });
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schema,
|
||||
};
|
||||
},
|
||||
select: (field, fields) => {
|
||||
const schema = {
|
||||
...formatBaseSchema(field),
|
||||
type: String,
|
||||
enum: field.options.map((option) => {
|
||||
if (typeof option === 'object') return option.value;
|
||||
return option;
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: field.hasMany ? [schema] : schema,
|
||||
};
|
||||
},
|
||||
flexible: (field, fields) => {
|
||||
const flexibleSchema = new Schema({ blockName: String }, { discriminatorKey: 'blockType', _id: false });
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: {
|
||||
type: [flexibleSchema],
|
||||
localized: field.localized || false,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = buildSchema;
|
||||
|
||||
@@ -111,7 +111,9 @@ module.exports = (config) => {
|
||||
filename: './index.html',
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new Dotenv(),
|
||||
new Dotenv({
|
||||
silent: true,
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
modules: ['node_modules', path.resolve(__dirname, '../../node_modules')],
|
||||
|
||||
Reference in New Issue
Block a user