implements initial form state
This commit is contained in:
@@ -16,7 +16,7 @@ module.exports = {
|
||||
read: () => true,
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc.title) {
|
||||
if (doc && doc.title) {
|
||||
return `http://localhost:3000/posts/${doc.title.value}?preview=true&token=${token}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
outline: 0;
|
||||
padding: base(.5);
|
||||
color: $color-dark-gray;
|
||||
line-height: 1;
|
||||
line-height: base(1);
|
||||
|
||||
&:hover:not(.clickable-arrow--is-disabled) {
|
||||
background: $color-background-gray;
|
||||
|
||||
@@ -8,7 +8,7 @@ import './index.scss';
|
||||
const baseClass = 'editable-block-title';
|
||||
|
||||
const EditableBlockTitle = (props) => {
|
||||
const { path, initialData } = props;
|
||||
const { path } = props;
|
||||
const inputRef = useRef(null);
|
||||
const inputCloneRef = useRef(null);
|
||||
const [inputWidth, setInputWidth] = useState(0);
|
||||
@@ -18,7 +18,6 @@ const EditableBlockTitle = (props) => {
|
||||
setValue,
|
||||
} = useFieldType({
|
||||
path,
|
||||
initialData,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -31,7 +30,7 @@ const EditableBlockTitle = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<React.Fragment>
|
||||
<div className={baseClass}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
@@ -53,17 +52,12 @@ const EditableBlockTitle = (props) => {
|
||||
>
|
||||
{value || 'Untitled'}
|
||||
</span>
|
||||
</>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
EditableBlockTitle.defaultProps = {
|
||||
initialData: undefined,
|
||||
};
|
||||
|
||||
EditableBlockTitle.propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
initialData: PropTypes.string,
|
||||
};
|
||||
|
||||
export default EditableBlockTitle;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AnimateHeight from 'react-animate-height';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
@@ -22,17 +22,16 @@ const DraggableSection = (props) => {
|
||||
rowCount,
|
||||
parentPath,
|
||||
fieldSchema,
|
||||
initialData,
|
||||
singularLabel,
|
||||
blockType,
|
||||
fieldTypes,
|
||||
customComponentsPath,
|
||||
isOpen,
|
||||
toggleRowCollapse,
|
||||
id,
|
||||
positionPanelVerticalAlignment,
|
||||
actionPanelVerticalAlignment,
|
||||
toggleRowCollapse,
|
||||
permissions,
|
||||
isOpen,
|
||||
} = props;
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
@@ -73,7 +72,6 @@ const DraggableSection = (props) => {
|
||||
<div className={`${baseClass}__section-header`}>
|
||||
<SectionTitle
|
||||
label={singularLabel}
|
||||
initialData={initialData?.blockName}
|
||||
path={`${parentPath}.${rowIndex}.blockName`}
|
||||
/>
|
||||
|
||||
@@ -92,7 +90,6 @@ const DraggableSection = (props) => {
|
||||
duration={0}
|
||||
>
|
||||
<RenderFields
|
||||
initialData={initialData}
|
||||
customComponentsPath={customComponentsPath}
|
||||
fieldTypes={fieldTypes}
|
||||
key={rowIndex}
|
||||
|
||||
101
src/client/components/forms/Form/buildStateFromSchema.js
Normal file
101
src/client/components/forms/Form/buildStateFromSchema.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const buildValidationPromise = async (fieldState, field) => {
|
||||
const validatedFieldState = fieldState;
|
||||
|
||||
validatedFieldState.valid = typeof field.validate === 'function' ? await field.validate(fieldState.value, field) : true;
|
||||
|
||||
if (typeof validatedFieldState.valid === 'string') {
|
||||
validatedFieldState.errorMessage = validatedFieldState.valid;
|
||||
validatedFieldState.valid = false;
|
||||
}
|
||||
};
|
||||
|
||||
const buildStateFromSchema = async (fieldSchema, fullData) => {
|
||||
if (fieldSchema && fullData) {
|
||||
const validationPromises = [];
|
||||
|
||||
const structureFieldState = (field, data = {}) => {
|
||||
const value = data[field.name] || field.defaultValue;
|
||||
|
||||
const fieldState = {
|
||||
value,
|
||||
initialValue: value,
|
||||
};
|
||||
|
||||
validationPromises.push(buildValidationPromise(fieldState, field));
|
||||
|
||||
return fieldState;
|
||||
};
|
||||
|
||||
const iterateFields = (fields, data, path = '') => fields.reduce((state, field) => {
|
||||
if (field.name && data[field.name]) {
|
||||
if (Array.isArray(data[field.name])) {
|
||||
if (field.type === 'array') {
|
||||
return {
|
||||
...state,
|
||||
...data[field.name].reduce((rowState, row, i) => ({
|
||||
...rowState,
|
||||
...iterateFields(field.fields, row, `${path}${field.name}.${i}.`),
|
||||
}), {}),
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === 'blocks') {
|
||||
return {
|
||||
...state,
|
||||
...data[field.name].reduce((rowState, row, i) => {
|
||||
const block = field.blocks.find((blockType) => blockType.slug === row.blockType);
|
||||
const rowPath = `${path}${field.name}.${i}.`;
|
||||
|
||||
return {
|
||||
...rowState,
|
||||
[`${rowPath}blockType`]: {
|
||||
value: row.blockType,
|
||||
initialValue: row.blockType,
|
||||
valid: true,
|
||||
},
|
||||
[`${rowPath}blockName`]: {
|
||||
value: row.blockName,
|
||||
initialValue: row.blockName,
|
||||
valid: true,
|
||||
},
|
||||
...iterateFields(block.fields, row, rowPath),
|
||||
};
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (field.fields) {
|
||||
return {
|
||||
...state,
|
||||
...iterateFields(field.fields, data[field.name], `${path}${field.name}.`),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
...state,
|
||||
[`${path}${field.name}`]: structureFieldState(field, data),
|
||||
};
|
||||
}
|
||||
|
||||
if (field.fields) {
|
||||
return {
|
||||
...state,
|
||||
...iterateFields(field.fields, data, path),
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}, {});
|
||||
|
||||
const resultingState = iterateFields(fieldSchema, fullData);
|
||||
await Promise.all(validationPromises);
|
||||
return resultingState;
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
module.exports = buildStateFromSchema;
|
||||
@@ -1,9 +1,43 @@
|
||||
import { unflatten, flatten } from 'flatley';
|
||||
import flattenFilters from './flattenFilters';
|
||||
|
||||
//
|
||||
const unflattenRowsFromState = (state, path) => {
|
||||
// Take a copy of state
|
||||
const remainingFlattenedState = { ...state };
|
||||
|
||||
const rowsFromStateObject = {};
|
||||
|
||||
const pathPrefixToRemove = path.substring(0, path.lastIndexOf('.') + 1);
|
||||
|
||||
// Loop over all keys from state
|
||||
// If the key begins with the name of the parent field,
|
||||
// Add value to rowsFromStateObject and delete it from remaining state
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (key.indexOf(`${path}.`) === 0) {
|
||||
if (!state[key].ignoreWhileFlattening) {
|
||||
const name = key.replace(pathPrefixToRemove, '');
|
||||
rowsFromStateObject[name] = state[key];
|
||||
rowsFromStateObject[name].initialValue = rowsFromStateObject[name].value;
|
||||
}
|
||||
|
||||
delete remainingFlattenedState[key];
|
||||
}
|
||||
});
|
||||
|
||||
const unflattenedRows = unflatten(rowsFromStateObject);
|
||||
|
||||
return {
|
||||
unflattenedRows: unflattenedRows[path.replace(pathPrefixToRemove, '')] || [],
|
||||
remainingFlattenedState,
|
||||
};
|
||||
};
|
||||
|
||||
function fieldReducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'REPLACE_ALL':
|
||||
return {
|
||||
...action.value,
|
||||
};
|
||||
case 'REPLACE_STATE': {
|
||||
return action.state;
|
||||
}
|
||||
|
||||
case 'REMOVE': {
|
||||
const newState = { ...state };
|
||||
@@ -11,15 +45,112 @@ function fieldReducer(state, action) {
|
||||
return newState;
|
||||
}
|
||||
|
||||
case 'REMOVE_ROW': {
|
||||
const { rowIndex, path } = action;
|
||||
const { unflattenedRows, remainingFlattenedState } = unflattenRowsFromState(state, path);
|
||||
|
||||
unflattenedRows.splice(rowIndex, 1);
|
||||
|
||||
const flattenedRowState = unflattenedRows.length > 0 ? flatten({ [path]: unflattenedRows }, { filters: flattenFilters }) : {};
|
||||
|
||||
return {
|
||||
...remainingFlattenedState,
|
||||
...flattenedRowState,
|
||||
};
|
||||
}
|
||||
|
||||
case 'ADD_ROW': {
|
||||
const {
|
||||
rowIndex, path, fieldSchema, blockType,
|
||||
} = action;
|
||||
const { unflattenedRows, remainingFlattenedState } = unflattenRowsFromState(state, path);
|
||||
|
||||
// Get paths of sub fields
|
||||
const subFields = fieldSchema.reduce((acc, field) => {
|
||||
if (field.type === 'flexible' || field.type === 'repeater') {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (field.name) {
|
||||
return {
|
||||
...acc,
|
||||
[field.name]: {
|
||||
value: null,
|
||||
valid: !field.required,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (field.fields) {
|
||||
return {
|
||||
...acc,
|
||||
...(field.fields.reduce((fields, subField) => ({
|
||||
...fields,
|
||||
[subField.name]: {
|
||||
value: null,
|
||||
valid: !field.required,
|
||||
},
|
||||
}), {})),
|
||||
};
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (blockType) {
|
||||
subFields.blockType = {
|
||||
value: blockType,
|
||||
initialValue: blockType,
|
||||
valid: true,
|
||||
};
|
||||
|
||||
subFields.blockName = {
|
||||
value: null,
|
||||
initialValue: null,
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Add new object containing subfield names to unflattenedRows array
|
||||
unflattenedRows.splice(rowIndex + 1, 0, subFields);
|
||||
|
||||
const newState = {
|
||||
...remainingFlattenedState,
|
||||
...(flatten({ [path]: unflattenedRows }, { filters: flattenFilters })),
|
||||
};
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
case 'MOVE_ROW': {
|
||||
const { moveFromIndex, moveToIndex, path } = action;
|
||||
const { unflattenedRows, remainingFlattenedState } = unflattenRowsFromState(state, path);
|
||||
|
||||
// copy the row to move
|
||||
const copyOfMovingRow = unflattenedRows[moveFromIndex];
|
||||
// delete the row by index
|
||||
unflattenedRows.splice(moveFromIndex, 1);
|
||||
// insert row copyOfMovingRow back in
|
||||
unflattenedRows.splice(moveToIndex, 0, copyOfMovingRow);
|
||||
|
||||
const newState = {
|
||||
...remainingFlattenedState,
|
||||
...(flatten({ [path]: unflattenedRows }, { filters: flattenFilters })),
|
||||
};
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
default: {
|
||||
const newField = {
|
||||
value: action.value,
|
||||
valid: action.valid,
|
||||
errorMessage: action.errorMessage,
|
||||
disableFormData: action.disableFormData,
|
||||
ignoreWhileFlattening: action.ignoreWhileFlattening,
|
||||
initialValue: action.initialValue,
|
||||
};
|
||||
|
||||
if (action.disableFormData) newField.disableFormData = action.disableFormData;
|
||||
|
||||
return {
|
||||
...state,
|
||||
[action.path]: newField,
|
||||
|
||||
10
src/client/components/forms/Form/flattenFilters.js
Normal file
10
src/client/components/forms/Form/flattenFilters.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const flattenFilters = [{
|
||||
test: (_, value) => {
|
||||
const hasValidProperty = Object.prototype.hasOwnProperty.call(value, 'valid');
|
||||
const hasValueProperty = Object.prototype.hasOwnProperty.call(value, 'value');
|
||||
|
||||
return (hasValidProperty && hasValueProperty);
|
||||
},
|
||||
}];
|
||||
|
||||
export default flattenFilters;
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, {
|
||||
useReducer, useEffect, useRef, useState,
|
||||
useReducer, useEffect, useRef, useState, useCallback,
|
||||
} from 'react';
|
||||
import { objectToFormData } from 'object-to-formdata';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { unflatten } from 'flatley';
|
||||
import HiddenInput from '../field-types/HiddenInput';
|
||||
import { useLocale } from '../../utilities/Locale';
|
||||
import { useStatusList } from '../../elements/Status';
|
||||
import { requests } from '../../../api';
|
||||
@@ -40,7 +39,6 @@ const Form = (props) => {
|
||||
const {
|
||||
disabled,
|
||||
onSubmit,
|
||||
ajax,
|
||||
method,
|
||||
action,
|
||||
handleResponse,
|
||||
@@ -49,6 +47,7 @@ const Form = (props) => {
|
||||
className,
|
||||
redirect,
|
||||
disableSuccessStatus,
|
||||
initialState,
|
||||
} = props;
|
||||
|
||||
const history = useHistory();
|
||||
@@ -62,11 +61,12 @@ const Form = (props) => {
|
||||
|
||||
const contextRef = useRef({ ...initContextState });
|
||||
|
||||
contextRef.current.initialState = initialState;
|
||||
|
||||
const [fields, dispatchFields] = useReducer(fieldReducer, {});
|
||||
contextRef.current.fields = fields;
|
||||
contextRef.current.dispatchFields = dispatchFields;
|
||||
|
||||
contextRef.current.submit = (e) => {
|
||||
const submit = useCallback((e) => {
|
||||
if (disabled) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
@@ -100,121 +100,129 @@ const Form = (props) => {
|
||||
return onSubmit(fields);
|
||||
}
|
||||
|
||||
// If form is AJAX, fetch data
|
||||
if (ajax !== false) {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
|
||||
const formData = contextRef.current.createFormData();
|
||||
setProcessing(true);
|
||||
const formData = contextRef.current.createFormData();
|
||||
setProcessing(true);
|
||||
|
||||
// Make the API call from the action
|
||||
return requests[method.toLowerCase()](action, {
|
||||
body: formData,
|
||||
}).then((res) => {
|
||||
setModified(false);
|
||||
if (typeof handleResponse === 'function') return handleResponse(res);
|
||||
// Make the API call from the action
|
||||
return requests[method.toLowerCase()](action, {
|
||||
body: formData,
|
||||
}).then((res) => {
|
||||
setModified(false);
|
||||
if (typeof handleResponse === 'function') return handleResponse(res);
|
||||
|
||||
return res.json().then((json) => {
|
||||
setProcessing(false);
|
||||
clearStatus();
|
||||
return res.json().then((json) => {
|
||||
setProcessing(false);
|
||||
clearStatus();
|
||||
|
||||
if (res.status < 400) {
|
||||
if (typeof onSuccess === 'function') onSuccess(json);
|
||||
if (res.status < 400) {
|
||||
if (typeof onSuccess === 'function') onSuccess(json);
|
||||
|
||||
if (redirect) {
|
||||
const destination = {
|
||||
pathname: redirect,
|
||||
if (redirect) {
|
||||
const destination = {
|
||||
pathname: redirect,
|
||||
};
|
||||
|
||||
if (json.message && !disableSuccessStatus) {
|
||||
destination.state = {
|
||||
status: [
|
||||
{
|
||||
message: json.message,
|
||||
type: 'success',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (json.message && !disableSuccessStatus) {
|
||||
destination.state = {
|
||||
status: [
|
||||
{
|
||||
message: json.message,
|
||||
type: 'success',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
history.push(destination);
|
||||
} else if (!disableSuccessStatus) {
|
||||
replaceStatus([{
|
||||
message: json.message,
|
||||
type: 'success',
|
||||
disappear: 3000,
|
||||
}]);
|
||||
}
|
||||
} else {
|
||||
if (json.message) {
|
||||
addStatus({
|
||||
message: json.message,
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
if (Array.isArray(json.errors)) {
|
||||
const [fieldErrors, nonFieldErrors] = json.errors.reduce(([fieldErrs, nonFieldErrs], err) => (err.field && err.message ? [[...fieldErrs, err], nonFieldErrs] : [fieldErrs, [...nonFieldErrs, err]]), [[], []]);
|
||||
|
||||
fieldErrors.forEach((err) => {
|
||||
dispatchFields({
|
||||
valid: false,
|
||||
errorMessage: err.message,
|
||||
path: err.field,
|
||||
value: contextRef.current.fields?.[err.field]?.value,
|
||||
});
|
||||
});
|
||||
|
||||
nonFieldErrors.forEach((err) => {
|
||||
addStatus({
|
||||
message: err.message || 'An unknown error occurred.',
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
|
||||
if (fieldErrors.length > 0 && nonFieldErrors.length === 0) {
|
||||
addStatus({
|
||||
message: 'Please correct the fields below.',
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
history.push(destination);
|
||||
} else if (!disableSuccessStatus) {
|
||||
replaceStatus([{
|
||||
message: json.message,
|
||||
type: 'success',
|
||||
disappear: 3000,
|
||||
}]);
|
||||
}
|
||||
} else {
|
||||
if (json.message) {
|
||||
addStatus({
|
||||
message: 'An unknown error occurred.',
|
||||
message: json.message,
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
return json;
|
||||
});
|
||||
}).catch((err) => {
|
||||
addStatus({
|
||||
message: err,
|
||||
type: 'error',
|
||||
});
|
||||
if (Array.isArray(json.errors)) {
|
||||
const [fieldErrors, nonFieldErrors] = json.errors.reduce(([fieldErrs, nonFieldErrs], err) => (err.field && err.message ? [[...fieldErrs, err], nonFieldErrs] : [fieldErrs, [...nonFieldErrs, err]]), [[], []]);
|
||||
|
||||
fieldErrors.forEach((err) => {
|
||||
dispatchFields({
|
||||
...(contextRef.current?.fields?.[err.field] || {}),
|
||||
valid: false,
|
||||
errorMessage: err.message,
|
||||
path: err.field,
|
||||
});
|
||||
});
|
||||
|
||||
nonFieldErrors.forEach((err) => {
|
||||
addStatus({
|
||||
message: err.message || 'An unknown error occurred.',
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
|
||||
if (fieldErrors.length > 0 && nonFieldErrors.length === 0) {
|
||||
addStatus({
|
||||
message: 'Please correct the fields below.',
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
addStatus({
|
||||
message: 'An unknown error occurred.',
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
return json;
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
addStatus({
|
||||
message: err,
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
}, [
|
||||
action,
|
||||
addStatus,
|
||||
clearStatus,
|
||||
disableSuccessStatus,
|
||||
disabled,
|
||||
fields,
|
||||
handleResponse,
|
||||
history,
|
||||
method,
|
||||
onSubmit,
|
||||
onSuccess,
|
||||
redirect,
|
||||
replaceStatus,
|
||||
]);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
contextRef.current.getFields = () => contextRef.current.fields;
|
||||
const getFields = useCallback(() => contextRef.current.fields, [contextRef]);
|
||||
const getField = useCallback((path) => contextRef.current.fields[path], [contextRef]);
|
||||
const getData = useCallback(() => reduceFieldsToValues(contextRef.current.fields, true), [contextRef]);
|
||||
|
||||
contextRef.current.getField = (path) => contextRef.current.fields[path];
|
||||
|
||||
contextRef.current.getData = () => reduceFieldsToValues(contextRef.current.fields, true);
|
||||
|
||||
contextRef.current.getSiblingData = (path) => {
|
||||
const getSiblingData = useCallback((path) => {
|
||||
let siblingFields = contextRef.current.fields;
|
||||
|
||||
// If this field is nested
|
||||
@@ -234,9 +242,9 @@ const Form = (props) => {
|
||||
}
|
||||
|
||||
return reduceFieldsToValues(siblingFields, true);
|
||||
};
|
||||
}, [contextRef]);
|
||||
|
||||
contextRef.current.getDataByPath = (path) => {
|
||||
const getDataByPath = useCallback((path) => {
|
||||
const pathPrefixToRemove = path.substring(0, path.lastIndexOf('.') + 1);
|
||||
const name = path.split('.').pop();
|
||||
|
||||
@@ -254,21 +262,35 @@ const Form = (props) => {
|
||||
const values = reduceFieldsToValues(data, true);
|
||||
const unflattenedData = unflatten(values);
|
||||
return unflattenedData?.[name];
|
||||
};
|
||||
}, [contextRef]);
|
||||
|
||||
contextRef.current.getUnflattenedValues = () => reduceFieldsToValues(contextRef.current.fields);
|
||||
const getUnflattenedValues = useCallback(() => reduceFieldsToValues(contextRef.current.fields), [contextRef]);
|
||||
|
||||
contextRef.current.validateForm = () => !Object.values(contextRef.current.fields).some((field) => field.valid === false);
|
||||
const validateForm = useCallback(() => !Object.values(contextRef.current.fields).some((field) => field.valid === false), [contextRef]);
|
||||
|
||||
contextRef.current.createFormData = () => {
|
||||
const createFormData = useCallback(() => {
|
||||
const data = reduceFieldsToValues(contextRef.current.fields);
|
||||
return objectToFormData(data, { indices: true });
|
||||
};
|
||||
}, [contextRef]);
|
||||
|
||||
contextRef.current.dispatchFields = dispatchFields;
|
||||
contextRef.current.submit = submit;
|
||||
contextRef.current.getFields = getFields;
|
||||
contextRef.current.getField = getField;
|
||||
contextRef.current.getData = getData;
|
||||
contextRef.current.getSiblingData = getSiblingData;
|
||||
contextRef.current.getDataByPath = getDataByPath;
|
||||
contextRef.current.getUnflattenedValues = getUnflattenedValues;
|
||||
contextRef.current.validateForm = validateForm;
|
||||
contextRef.current.createFormData = createFormData;
|
||||
contextRef.current.setModified = setModified;
|
||||
contextRef.current.setProcessing = setProcessing;
|
||||
contextRef.current.setSubmitted = setSubmitted;
|
||||
|
||||
useEffect(() => {
|
||||
dispatchFields({ type: 'REPLACE_STATE', state: initialState });
|
||||
}, [initialState]);
|
||||
|
||||
useThrottledEffect(() => {
|
||||
refreshCookie();
|
||||
}, 15000, [fields]);
|
||||
@@ -299,10 +321,6 @@ const Form = (props) => {
|
||||
<SubmittedContext.Provider value={submitted}>
|
||||
<ProcessingContext.Provider value={processing}>
|
||||
<ModifiedContext.Provider value={modified}>
|
||||
<HiddenInput
|
||||
path="locale"
|
||||
defaultValue={locale}
|
||||
/>
|
||||
{children}
|
||||
</ModifiedContext.Provider>
|
||||
</ProcessingContext.Provider>
|
||||
@@ -317,7 +335,6 @@ const Form = (props) => {
|
||||
Form.defaultProps = {
|
||||
redirect: '',
|
||||
onSubmit: null,
|
||||
ajax: true,
|
||||
method: 'POST',
|
||||
action: '',
|
||||
handleResponse: null,
|
||||
@@ -325,12 +342,12 @@ Form.defaultProps = {
|
||||
className: '',
|
||||
disableSuccessStatus: false,
|
||||
disabled: false,
|
||||
initialState: {},
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
disableSuccessStatus: PropTypes.bool,
|
||||
onSubmit: PropTypes.func,
|
||||
ajax: PropTypes.bool,
|
||||
method: PropTypes.oneOf(['post', 'POST', 'get', 'GET', 'put', 'PUT', 'delete', 'DELETE']),
|
||||
action: PropTypes.string,
|
||||
handleResponse: PropTypes.func,
|
||||
@@ -342,6 +359,7 @@ Form.propTypes = {
|
||||
className: PropTypes.string,
|
||||
redirect: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
initialState: PropTypes.shape({}),
|
||||
};
|
||||
|
||||
export default Form;
|
||||
|
||||
@@ -10,4 +10,6 @@ export default {
|
||||
submit: () => { },
|
||||
dispatchFields: () => { },
|
||||
setModified: () => { },
|
||||
initialState: {},
|
||||
reset: 0,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import RenderCustomComponent from '../../utilities/RenderCustomComponent';
|
||||
import useIntersect from '../../../hooks/useIntersect';
|
||||
|
||||
import './index.scss';
|
||||
const baseClass = 'render-fields';
|
||||
|
||||
const intersectionObserverOptions = {
|
||||
rootMargin: '1000px',
|
||||
@@ -16,13 +16,13 @@ export const useRenderedFields = () => useContext(RenderedFieldContext);
|
||||
const RenderFields = (props) => {
|
||||
const {
|
||||
fieldSchema,
|
||||
initialData,
|
||||
customComponentsPath: customComponentsPathFromProps,
|
||||
fieldTypes,
|
||||
filter,
|
||||
permissions,
|
||||
readOnly: readOnlyOverride,
|
||||
operation: operationFromProps,
|
||||
className,
|
||||
} = props;
|
||||
|
||||
const [hasIntersected, setHasIntersected] = useState(false);
|
||||
@@ -52,9 +52,17 @@ const RenderFields = (props) => {
|
||||
}
|
||||
}, [isIntersecting, hasIntersected]);
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
className,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
if (fieldSchema) {
|
||||
return (
|
||||
<div ref={intersectionRef}>
|
||||
<div
|
||||
ref={intersectionRef}
|
||||
className={classes}
|
||||
>
|
||||
{hasIntersected && (
|
||||
<RenderedFieldContext.Provider value={contextValue}>
|
||||
{fieldSchema.map((field, i) => {
|
||||
@@ -62,14 +70,10 @@ const RenderFields = (props) => {
|
||||
if ((filter && typeof filter === 'function' && filter(field)) || !filter) {
|
||||
const FieldComponent = field?.admin?.hidden ? fieldTypes.hidden : fieldTypes[field.type];
|
||||
|
||||
let initialFieldData;
|
||||
let fieldPermissions = permissions[field.name];
|
||||
|
||||
if (!field.name) {
|
||||
initialFieldData = initialData;
|
||||
fieldPermissions = permissions;
|
||||
} else if (initialData?.[field.name] !== undefined) {
|
||||
initialFieldData = initialData[field.name];
|
||||
}
|
||||
|
||||
let { admin: { readOnly } = {} } = field;
|
||||
@@ -84,14 +88,13 @@ const RenderFields = (props) => {
|
||||
if (FieldComponent) {
|
||||
return (
|
||||
<RenderCustomComponent
|
||||
key={field.name || `field-${i}`}
|
||||
key={i}
|
||||
path={`${customComponentsPath}${field.name ? `${field.name}.field` : ''}`}
|
||||
DefaultComponent={FieldComponent}
|
||||
componentProps={{
|
||||
...field,
|
||||
path: field.path || field.name,
|
||||
fieldTypes,
|
||||
initialData: initialFieldData,
|
||||
admin: {
|
||||
...(field.admin || {}),
|
||||
readOnly,
|
||||
@@ -132,19 +135,18 @@ const RenderFields = (props) => {
|
||||
};
|
||||
|
||||
RenderFields.defaultProps = {
|
||||
initialData: {},
|
||||
customComponentsPath: '',
|
||||
filter: null,
|
||||
readOnly: false,
|
||||
permissions: {},
|
||||
operation: undefined,
|
||||
className: undefined,
|
||||
};
|
||||
|
||||
RenderFields.propTypes = {
|
||||
fieldSchema: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
).isRequired,
|
||||
initialData: PropTypes.shape({}),
|
||||
customComponentsPath: PropTypes.string,
|
||||
fieldTypes: PropTypes.shape({
|
||||
hidden: PropTypes.function,
|
||||
@@ -153,6 +155,7 @@ RenderFields.propTypes = {
|
||||
permissions: PropTypes.shape({}),
|
||||
readOnly: PropTypes.bool,
|
||||
operation: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default RenderFields;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.missing-field {
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useReducer, useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import withCondition from '../../withCondition';
|
||||
import Button from '../../../elements/Button';
|
||||
@@ -23,8 +22,6 @@ const ArrayFieldType = (props) => {
|
||||
name,
|
||||
path: pathFromProps,
|
||||
fields,
|
||||
defaultValue,
|
||||
initialData,
|
||||
fieldTypes,
|
||||
validate,
|
||||
required,
|
||||
@@ -34,10 +31,9 @@ const ArrayFieldType = (props) => {
|
||||
permissions,
|
||||
} = props;
|
||||
|
||||
const dataToInitialize = initialData || defaultValue;
|
||||
const [rows, dispatchRows] = useReducer(reducer, []);
|
||||
const { customComponentsPath } = useRenderedFields();
|
||||
const { getDataByPath } = useForm();
|
||||
const { getDataByPath, initialState, dispatchFields } = useForm();
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
@@ -57,40 +53,25 @@ const ArrayFieldType = (props) => {
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
initialData: initialData?.length,
|
||||
defaultValue: defaultValue?.length,
|
||||
ignoreWhileFlattening: true,
|
||||
required,
|
||||
});
|
||||
|
||||
const addRow = useCallback((rowIndex) => {
|
||||
const data = getDataByPath(path);
|
||||
|
||||
dispatchRows({
|
||||
type: 'ADD', index: rowIndex, data,
|
||||
});
|
||||
|
||||
dispatchRows({ type: 'ADD', rowIndex });
|
||||
dispatchFields({ type: 'ADD_ROW', rowIndex, fieldSchema: fields, path });
|
||||
setValue(value + 1);
|
||||
}, [dispatchRows, getDataByPath, path, setValue, value]);
|
||||
}, [dispatchRows, dispatchFields, fields, path, setValue, value]);
|
||||
|
||||
const removeRow = useCallback((rowIndex) => {
|
||||
const data = getDataByPath(path);
|
||||
|
||||
dispatchRows({
|
||||
type: 'REMOVE',
|
||||
index: rowIndex,
|
||||
data,
|
||||
});
|
||||
|
||||
setValue(value - 1);
|
||||
}, [dispatchRows, path, getDataByPath, setValue, value]);
|
||||
dispatchRows({ type: 'REMOVE', rowIndex });
|
||||
dispatchFields({ type: 'REMOVE_ROW', rowIndex, path });
|
||||
}, [dispatchRows, dispatchFields, path]);
|
||||
|
||||
const moveRow = useCallback((moveFromIndex, moveToIndex) => {
|
||||
const data = getDataByPath(path);
|
||||
|
||||
dispatchRows({
|
||||
type: 'MOVE', index: moveFromIndex, moveToIndex, data,
|
||||
});
|
||||
}, [dispatchRows, getDataByPath, path]);
|
||||
dispatchRows({ type: 'MOVE', moveFromIndex, moveToIndex });
|
||||
dispatchFields({ type: 'MOVE_ROW', moveFromIndex, moveToIndex, path });
|
||||
}, [dispatchRows, dispatchFields, path]);
|
||||
|
||||
const onDragEnd = useCallback((result) => {
|
||||
if (!result.destination) return;
|
||||
@@ -100,28 +81,19 @@ const ArrayFieldType = (props) => {
|
||||
}, [moveRow]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatchRows({
|
||||
type: 'SET_ALL',
|
||||
rows: dataToInitialize.reduce((acc, data) => ([
|
||||
...acc,
|
||||
{
|
||||
key: uuidv4(),
|
||||
open: true,
|
||||
data,
|
||||
},
|
||||
]), []),
|
||||
});
|
||||
}, [dataToInitialize]);
|
||||
const data = getDataByPath(path);
|
||||
dispatchRows({ type: 'SET_ALL', data });
|
||||
}, [initialState, getDataByPath, path]);
|
||||
|
||||
useEffect(() => {
|
||||
if (value === 0 && dataToInitialize.length > 0 && disableFormData) {
|
||||
setValue(rows?.length || 0);
|
||||
|
||||
if (rows?.length === 0) {
|
||||
setDisableFormData(false);
|
||||
setValue(value);
|
||||
} else if (value > 0 && !disableFormData) {
|
||||
} else {
|
||||
setDisableFormData(true);
|
||||
setValue(value);
|
||||
}
|
||||
}, [value, setValue, disableFormData, dataToInitialize]);
|
||||
}, [rows, setValue]);
|
||||
|
||||
return (
|
||||
<RenderArray
|
||||
@@ -147,8 +119,6 @@ const ArrayFieldType = (props) => {
|
||||
|
||||
ArrayFieldType.defaultProps = {
|
||||
label: '',
|
||||
defaultValue: [],
|
||||
initialData: [],
|
||||
validate: array,
|
||||
required: false,
|
||||
maxRows: undefined,
|
||||
@@ -158,12 +128,6 @@ ArrayFieldType.defaultProps = {
|
||||
};
|
||||
|
||||
ArrayFieldType.propTypes = {
|
||||
defaultValue: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
),
|
||||
initialData: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
),
|
||||
fields: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
).isRequired,
|
||||
@@ -230,7 +194,6 @@ const RenderArray = React.memo((props) => {
|
||||
removeRow={() => removeRow(i)}
|
||||
moveRow={moveRow}
|
||||
parentPath={path}
|
||||
initialData={row.data}
|
||||
initNull={row.initNull}
|
||||
customComponentsPath={`${customComponentsPath}${name}.fields.`}
|
||||
fieldTypes={fieldTypes}
|
||||
@@ -259,4 +222,40 @@ const RenderArray = React.memo((props) => {
|
||||
);
|
||||
});
|
||||
|
||||
RenderArray.defaultProps = {
|
||||
label: undefined,
|
||||
showError: false,
|
||||
errorMessage: undefined,
|
||||
rows: [],
|
||||
singularLabel: 'Row',
|
||||
path: '',
|
||||
customComponentsPath: undefined,
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
RenderArray.propTypes = {
|
||||
label: PropTypes.string,
|
||||
showError: PropTypes.bool,
|
||||
errorMessage: PropTypes.string,
|
||||
rows: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
),
|
||||
singularLabel: PropTypes.string,
|
||||
path: PropTypes.string,
|
||||
customComponentsPath: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.number,
|
||||
onDragEnd: PropTypes.func.isRequired,
|
||||
addRow: PropTypes.func.isRequired,
|
||||
removeRow: PropTypes.func.isRequired,
|
||||
moveRow: PropTypes.func.isRequired,
|
||||
fieldTypes: PropTypes.shape({}).isRequired,
|
||||
fields: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
).isRequired,
|
||||
permissions: PropTypes.shape({
|
||||
fields: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default withCondition(ArrayFieldType);
|
||||
|
||||
@@ -22,10 +22,10 @@ const BlockSelection = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
className={baseClass}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={handleBlockSelection}
|
||||
>
|
||||
<div className={`${baseClass}__image`}>
|
||||
@@ -36,11 +36,10 @@ const BlockSelection = (props) => {
|
||||
alt={blockImageAltText}
|
||||
/>
|
||||
)
|
||||
: <DefaultBlockImage />
|
||||
}
|
||||
: <DefaultBlockImage />}
|
||||
</div>
|
||||
<div className={`${baseClass}__label`}>{labels.singular}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
padding: base(.75) base(.5);
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
box-shadow: 0;
|
||||
border: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-background-gray;
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, {
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import withCondition from '../../withCondition';
|
||||
import Button from '../../../elements/Button';
|
||||
@@ -27,8 +26,6 @@ const Blocks = (props) => {
|
||||
name,
|
||||
path: pathFromProps,
|
||||
blocks,
|
||||
defaultValue,
|
||||
initialData,
|
||||
singularLabel,
|
||||
fieldTypes,
|
||||
maxRows,
|
||||
@@ -40,6 +37,10 @@ const Blocks = (props) => {
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const [rows, dispatchRows] = useReducer(reducer, []);
|
||||
const { customComponentsPath } = useRenderedFields();
|
||||
const { getDataByPath, initialState, dispatchFields } = useForm();
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(
|
||||
value,
|
||||
@@ -61,51 +62,32 @@ const Blocks = (props) => {
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
initialData: initialData?.length,
|
||||
defaultValue: defaultValue?.length,
|
||||
ignoreWhileFlattening: true,
|
||||
required,
|
||||
});
|
||||
|
||||
const dataToInitialize = initialData || defaultValue;
|
||||
const [rows, dispatchRows] = useReducer(reducer, []);
|
||||
const { customComponentsPath } = useRenderedFields();
|
||||
const { getDataByPath } = useForm();
|
||||
|
||||
const addRow = useCallback((index, blockType) => {
|
||||
const data = getDataByPath(path);
|
||||
|
||||
dispatchRows({
|
||||
type: 'ADD', index, data, initialRowData: { blockType },
|
||||
});
|
||||
const addRow = useCallback((rowIndex, blockType) => {
|
||||
const block = blocks.find((potentialBlock) => potentialBlock.slug === blockType);
|
||||
|
||||
dispatchRows({ type: 'ADD', rowIndex, blockType });
|
||||
dispatchFields({ type: 'ADD_ROW', rowIndex, fieldSchema: block.fields, path, blockType });
|
||||
setValue(value + 1);
|
||||
}, [getDataByPath, path, setValue, value]);
|
||||
|
||||
const removeRow = useCallback((index) => {
|
||||
const data = getDataByPath(path);
|
||||
|
||||
dispatchRows({
|
||||
type: 'REMOVE',
|
||||
index,
|
||||
data,
|
||||
});
|
||||
}, [path, setValue, value, blocks, dispatchFields]);
|
||||
|
||||
const removeRow = useCallback((rowIndex) => {
|
||||
dispatchRows({ type: 'REMOVE', rowIndex });
|
||||
dispatchFields({ type: 'REMOVE_ROW', rowIndex, path });
|
||||
setValue(value - 1);
|
||||
}, [getDataByPath, path, setValue, value]);
|
||||
}, [path, setValue, value, dispatchFields]);
|
||||
|
||||
const moveRow = useCallback((moveFromIndex, moveToIndex) => {
|
||||
const data = getDataByPath(path);
|
||||
dispatchRows({ type: 'MOVE', moveFromIndex, moveToIndex });
|
||||
dispatchFields({ type: 'MOVE_ROW', moveFromIndex, moveToIndex, path });
|
||||
}, [dispatchRows, dispatchFields, path]);
|
||||
|
||||
dispatchRows({
|
||||
type: 'MOVE', index: moveFromIndex, moveToIndex, data,
|
||||
});
|
||||
}, [getDataByPath, path]);
|
||||
|
||||
const toggleCollapse = useCallback((index) => {
|
||||
dispatchRows({
|
||||
type: 'TOGGLE_COLLAPSE', index, rows,
|
||||
});
|
||||
}, [rows]);
|
||||
const toggleCollapse = useCallback((rowIndex) => {
|
||||
dispatchRows({ type: 'TOGGLE_COLLAPSE', rowIndex });
|
||||
}, []);
|
||||
|
||||
const onDragEnd = useCallback((result) => {
|
||||
if (!result.destination) return;
|
||||
@@ -115,31 +97,22 @@ const Blocks = (props) => {
|
||||
}, [moveRow]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatchRows({
|
||||
type: 'SET_ALL',
|
||||
rows: dataToInitialize.reduce((acc, data) => ([
|
||||
...acc,
|
||||
{
|
||||
key: uuidv4(),
|
||||
open: true,
|
||||
data,
|
||||
},
|
||||
]), []),
|
||||
});
|
||||
}, [dataToInitialize]);
|
||||
const data = getDataByPath(path);
|
||||
dispatchRows({ type: 'SET_ALL', data });
|
||||
}, [initialState, getDataByPath, path]);
|
||||
|
||||
useEffect(() => {
|
||||
if (value === 0 && dataToInitialize.length > 0 && disableFormData) {
|
||||
setValue(rows?.length || 0);
|
||||
|
||||
if (rows?.length === 0) {
|
||||
setDisableFormData(false);
|
||||
setValue(value);
|
||||
} else if (value > 0 && !disableFormData) {
|
||||
} else {
|
||||
setDisableFormData(true);
|
||||
setValue(value);
|
||||
}
|
||||
}, [value, setValue, disableFormData, dataToInitialize]);
|
||||
}, [rows, setValue]);
|
||||
|
||||
return (
|
||||
<RenderBlock
|
||||
<RenderBlocks
|
||||
onDragEnd={onDragEnd}
|
||||
label={label}
|
||||
showError={showError}
|
||||
@@ -156,7 +129,6 @@ const Blocks = (props) => {
|
||||
toggleCollapse={toggleCollapse}
|
||||
permissions={permissions}
|
||||
value={value}
|
||||
dataToInitialize={dataToInitialize}
|
||||
blocks={blocks}
|
||||
/>
|
||||
);
|
||||
@@ -164,8 +136,6 @@ const Blocks = (props) => {
|
||||
|
||||
Blocks.defaultProps = {
|
||||
label: '',
|
||||
defaultValue: [],
|
||||
initialData: [],
|
||||
singularLabel: 'Block',
|
||||
validate: blocksValidator,
|
||||
required: false,
|
||||
@@ -178,12 +148,6 @@ Blocks.propTypes = {
|
||||
blocks: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
).isRequired,
|
||||
defaultValue: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
),
|
||||
initialData: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
),
|
||||
label: PropTypes.string,
|
||||
singularLabel: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
@@ -198,7 +162,7 @@ Blocks.propTypes = {
|
||||
}),
|
||||
};
|
||||
|
||||
const RenderBlock = React.memo((props) => {
|
||||
const RenderBlocks = React.memo((props) => {
|
||||
const {
|
||||
onDragEnd,
|
||||
label,
|
||||
@@ -216,7 +180,6 @@ const RenderBlock = React.memo((props) => {
|
||||
permissions,
|
||||
value,
|
||||
toggleCollapse,
|
||||
dataToInitialize,
|
||||
blocks,
|
||||
} = props;
|
||||
|
||||
@@ -239,12 +202,7 @@ const RenderBlock = React.memo((props) => {
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{rows.length > 0 && rows.map((row, i) => {
|
||||
let { blockType } = row.data;
|
||||
|
||||
if (!blockType) {
|
||||
blockType = dataToInitialize?.[i]?.blockType;
|
||||
}
|
||||
|
||||
const { blockType } = row;
|
||||
const blockToRender = blocks.find((block) => block.slug === blockType);
|
||||
|
||||
if (blockToRender) {
|
||||
@@ -263,7 +221,6 @@ const RenderBlock = React.memo((props) => {
|
||||
moveRow={moveRow}
|
||||
toggleRowCollapse={() => toggleCollapse(i)}
|
||||
parentPath={path}
|
||||
initialData={row.data}
|
||||
customComponentsPath={`${customComponentsPath}${name}.fields.`}
|
||||
fieldTypes={fieldTypes}
|
||||
permissions={permissions.fields}
|
||||
@@ -318,4 +275,41 @@ const RenderBlock = React.memo((props) => {
|
||||
);
|
||||
});
|
||||
|
||||
RenderBlocks.defaultProps = {
|
||||
label: undefined,
|
||||
showError: false,
|
||||
errorMessage: undefined,
|
||||
rows: [],
|
||||
singularLabel: 'Row',
|
||||
path: '',
|
||||
customComponentsPath: undefined,
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
RenderBlocks.propTypes = {
|
||||
label: PropTypes.string,
|
||||
showError: PropTypes.bool,
|
||||
errorMessage: PropTypes.string,
|
||||
rows: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
),
|
||||
singularLabel: PropTypes.string,
|
||||
path: PropTypes.string,
|
||||
customComponentsPath: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.number,
|
||||
onDragEnd: PropTypes.func.isRequired,
|
||||
addRow: PropTypes.func.isRequired,
|
||||
removeRow: PropTypes.func.isRequired,
|
||||
moveRow: PropTypes.func.isRequired,
|
||||
fieldTypes: PropTypes.shape({}).isRequired,
|
||||
permissions: PropTypes.shape({
|
||||
fields: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
blocks: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
).isRequired,
|
||||
toggleCollapse: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withCondition(Blocks);
|
||||
|
||||
@@ -15,8 +15,6 @@ const Checkbox = (props) => {
|
||||
name,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
label,
|
||||
onChange,
|
||||
@@ -43,8 +41,6 @@ const Checkbox = (props) => {
|
||||
} = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
});
|
||||
@@ -98,8 +94,6 @@ Checkbox.defaultProps = {
|
||||
label: null,
|
||||
required: false,
|
||||
admin: {},
|
||||
defaultValue: false,
|
||||
initialData: false,
|
||||
validate: checkbox,
|
||||
path: '',
|
||||
onChange: undefined,
|
||||
@@ -115,8 +109,6 @@ Checkbox.propTypes = {
|
||||
width: PropTypes.string,
|
||||
}),
|
||||
required: PropTypes.bool,
|
||||
defaultValue: PropTypes.bool,
|
||||
initialData: PropTypes.bool,
|
||||
validate: PropTypes.func,
|
||||
label: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
|
||||
@@ -19,8 +19,6 @@ const DateTime = (props) => {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
errorMessage,
|
||||
label,
|
||||
@@ -45,8 +43,6 @@ const DateTime = (props) => {
|
||||
} = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
});
|
||||
|
||||
@@ -88,8 +84,6 @@ const DateTime = (props) => {
|
||||
DateTime.defaultProps = {
|
||||
label: null,
|
||||
required: false,
|
||||
defaultValue: undefined,
|
||||
initialData: undefined,
|
||||
validate: date,
|
||||
errorMessage: defaultError,
|
||||
admin: {},
|
||||
@@ -101,8 +95,6 @@ DateTime.propTypes = {
|
||||
path: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
defaultValue: PropTypes.string,
|
||||
initialData: PropTypes.string,
|
||||
validate: PropTypes.func,
|
||||
errorMessage: PropTypes.string,
|
||||
admin: PropTypes.shape({
|
||||
|
||||
@@ -13,8 +13,6 @@ const Email = (props) => {
|
||||
name,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
admin: {
|
||||
readOnly,
|
||||
@@ -35,9 +33,6 @@ const Email = (props) => {
|
||||
|
||||
const fieldType = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import './index.scss';
|
||||
|
||||
const Group = (props) => {
|
||||
const {
|
||||
label, fields, name, path: pathFromProps, fieldTypes, initialData,
|
||||
label, fields, name, path: pathFromProps, fieldTypes,
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
@@ -18,7 +18,6 @@ const Group = (props) => {
|
||||
<div className="field-type group">
|
||||
<h3>{label}</h3>
|
||||
<RenderFields
|
||||
initialData={initialData}
|
||||
fieldTypes={fieldTypes}
|
||||
customComponentsPath={`${customComponentsPath}${name}.fields.`}
|
||||
fieldSchema={fields.map((subField) => ({
|
||||
@@ -32,12 +31,10 @@ const Group = (props) => {
|
||||
|
||||
Group.defaultProps = {
|
||||
label: '',
|
||||
initialData: {},
|
||||
path: '',
|
||||
};
|
||||
|
||||
Group.propTypes = {
|
||||
initialData: PropTypes.shape({}),
|
||||
fields: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
).isRequired,
|
||||
|
||||
@@ -8,8 +8,6 @@ const HiddenInput = (props) => {
|
||||
name,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
@@ -17,8 +15,6 @@ const HiddenInput = (props) => {
|
||||
const { value, setValue } = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -33,8 +29,6 @@ const HiddenInput = (props) => {
|
||||
|
||||
HiddenInput.defaultProps = {
|
||||
required: false,
|
||||
defaultValue: undefined,
|
||||
initialData: undefined,
|
||||
path: '',
|
||||
};
|
||||
|
||||
@@ -42,8 +36,6 @@ HiddenInput.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
defaultValue: PropTypes.string,
|
||||
initialData: PropTypes.string,
|
||||
};
|
||||
|
||||
export default withCondition(HiddenInput);
|
||||
|
||||
@@ -13,8 +13,6 @@ const NumberField = (props) => {
|
||||
name,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
label,
|
||||
placeholder,
|
||||
@@ -41,9 +39,6 @@ const NumberField = (props) => {
|
||||
errorMessage,
|
||||
} = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
});
|
||||
@@ -95,8 +90,6 @@ NumberField.defaultProps = {
|
||||
label: null,
|
||||
path: undefined,
|
||||
required: false,
|
||||
defaultValue: undefined,
|
||||
initialData: undefined,
|
||||
placeholder: undefined,
|
||||
max: undefined,
|
||||
min: undefined,
|
||||
@@ -109,8 +102,6 @@ NumberField.propTypes = {
|
||||
path: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
defaultValue: PropTypes.number,
|
||||
initialData: PropTypes.number,
|
||||
validate: PropTypes.func,
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
|
||||
@@ -13,8 +13,6 @@ const Password = (props) => {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
style,
|
||||
width,
|
||||
@@ -38,8 +36,6 @@ const Password = (props) => {
|
||||
} = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
});
|
||||
@@ -82,8 +78,6 @@ const Password = (props) => {
|
||||
|
||||
Password.defaultProps = {
|
||||
required: false,
|
||||
initialData: undefined,
|
||||
defaultValue: undefined,
|
||||
validate: password,
|
||||
width: undefined,
|
||||
style: {},
|
||||
@@ -95,8 +89,6 @@ Password.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
initialData: PropTypes.string,
|
||||
defaultValue: PropTypes.string,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
label: PropTypes.string.isRequired,
|
||||
|
||||
@@ -15,8 +15,6 @@ const RadioGroup = (props) => {
|
||||
name,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
label,
|
||||
admin: {
|
||||
@@ -42,8 +40,6 @@ const RadioGroup = (props) => {
|
||||
} = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
});
|
||||
|
||||
@@ -72,7 +68,7 @@ const RadioGroup = (props) => {
|
||||
required={required}
|
||||
/>
|
||||
{options?.map((option) => {
|
||||
const isSelected = !value ? (option.value === defaultValue) : (option.value === value);
|
||||
const isSelected = option.value === value;
|
||||
|
||||
return (
|
||||
<RadioInput
|
||||
@@ -90,8 +86,6 @@ const RadioGroup = (props) => {
|
||||
RadioGroup.defaultProps = {
|
||||
label: null,
|
||||
required: false,
|
||||
defaultValue: null,
|
||||
initialData: undefined,
|
||||
validate: radio,
|
||||
admin: {},
|
||||
path: '',
|
||||
@@ -101,8 +95,6 @@ RadioGroup.propTypes = {
|
||||
path: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
required: PropTypes.bool,
|
||||
defaultValue: PropTypes.string,
|
||||
initialData: PropTypes.string,
|
||||
validate: PropTypes.func,
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {
|
||||
Component, useState, useEffect, useCallback,
|
||||
Component, useCallback,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import some from 'async-some';
|
||||
@@ -90,7 +90,7 @@ class Relationship extends Component {
|
||||
error = 'You do not have permission to load options for this field.';
|
||||
}
|
||||
|
||||
this.setState({
|
||||
return this.setState({
|
||||
errorLoading: error,
|
||||
});
|
||||
}, (lastPage, nextPage) => {
|
||||
@@ -245,10 +245,6 @@ class Relationship extends Component {
|
||||
|
||||
const valueToRender = this.findValueInOptions(options, value);
|
||||
|
||||
// ///////////////////////////////////////////
|
||||
// TODO: simplify formatValue pattern seen below with react select
|
||||
// ///////////////////////////////////////////
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
@@ -330,14 +326,11 @@ Relationship.propTypes = {
|
||||
};
|
||||
|
||||
const RelationshipFieldType = (props) => {
|
||||
const [formattedInitialData, setFormattedInitialData] = useState(undefined);
|
||||
|
||||
const {
|
||||
defaultValue, relationTo, hasMany, validate, path, name, initialData, required,
|
||||
relationTo, validate, path, name, required,
|
||||
} = props;
|
||||
|
||||
const hasMultipleRelations = Array.isArray(relationTo);
|
||||
const dataToInitialize = initialData || defaultValue;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
@@ -348,41 +341,10 @@ const RelationshipFieldType = (props) => {
|
||||
const fieldType = useFieldType({
|
||||
...props,
|
||||
path: path || name,
|
||||
initialData: formattedInitialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
required,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const formatInitialData = (valueToFormat) => {
|
||||
if (hasMultipleRelations) {
|
||||
const id = valueToFormat?.value?.id || valueToFormat?.value;
|
||||
|
||||
return {
|
||||
...valueToFormat,
|
||||
value: id,
|
||||
};
|
||||
}
|
||||
|
||||
return valueToFormat?.id || valueToFormat;
|
||||
};
|
||||
|
||||
if (dataToInitialize) {
|
||||
if (hasMany && Array.isArray(dataToInitialize)) {
|
||||
const newFormattedInitialData = [];
|
||||
|
||||
dataToInitialize.forEach((individualValue) => {
|
||||
newFormattedInitialData.push(formatInitialData(individualValue));
|
||||
});
|
||||
|
||||
setFormattedInitialData(newFormattedInitialData);
|
||||
} else {
|
||||
setFormattedInitialData(formatInitialData(dataToInitialize));
|
||||
}
|
||||
}
|
||||
}, [dataToInitialize, hasMany, hasMultipleRelations]);
|
||||
|
||||
return (
|
||||
<Relationship
|
||||
{...props}
|
||||
|
||||
@@ -74,8 +74,6 @@ const RichText = (props) => {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
label,
|
||||
placeholder,
|
||||
@@ -93,8 +91,6 @@ const RichText = (props) => {
|
||||
const fieldType = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
});
|
||||
|
||||
@@ -164,8 +160,6 @@ const RichText = (props) => {
|
||||
RichText.defaultProps = {
|
||||
label: null,
|
||||
required: false,
|
||||
defaultValue: undefined,
|
||||
initialData: undefined,
|
||||
placeholder: undefined,
|
||||
admin: {},
|
||||
validate: richText,
|
||||
@@ -177,8 +171,6 @@ RichText.propTypes = {
|
||||
path: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
defaultValue: PropTypes.string,
|
||||
initialData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
validate: PropTypes.func,
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
|
||||
@@ -7,29 +7,24 @@ import './index.scss';
|
||||
|
||||
const Row = (props) => {
|
||||
const {
|
||||
fields, fieldTypes, initialData, path, permissions,
|
||||
fields, fieldTypes, path, permissions,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="field-type row">
|
||||
<RenderFields
|
||||
permissions={permissions}
|
||||
initialData={initialData}
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields.map((field) => {
|
||||
return {
|
||||
...field,
|
||||
path: `${path ? `${path}.` : ''}${field.name}`,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<RenderFields
|
||||
className="field-type row"
|
||||
permissions={permissions}
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields.map((field) => ({
|
||||
...field,
|
||||
path: `${path ? `${path}.` : ''}${field.name}`,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Row.defaultProps = {
|
||||
path: '',
|
||||
initialData: undefined,
|
||||
permissions: {},
|
||||
};
|
||||
|
||||
@@ -39,7 +34,6 @@ Row.propTypes = {
|
||||
).isRequired,
|
||||
fieldTypes: PropTypes.shape({}).isRequired,
|
||||
path: PropTypes.string,
|
||||
initialData: PropTypes.shape({}),
|
||||
permissions: PropTypes.shape({}),
|
||||
};
|
||||
|
||||
|
||||
@@ -65,8 +65,6 @@ const Select = (props) => {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
label,
|
||||
options,
|
||||
@@ -94,8 +92,6 @@ const Select = (props) => {
|
||||
path,
|
||||
label,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
});
|
||||
|
||||
@@ -142,8 +138,6 @@ Select.defaultProps = {
|
||||
admin: {},
|
||||
required: false,
|
||||
validate: select,
|
||||
defaultValue: undefined,
|
||||
initialData: undefined,
|
||||
hasMany: false,
|
||||
path: '',
|
||||
};
|
||||
@@ -156,14 +150,6 @@ Select.propTypes = {
|
||||
width: PropTypes.string,
|
||||
}),
|
||||
label: PropTypes.string.isRequired,
|
||||
defaultValue: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.array,
|
||||
]),
|
||||
initialData: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.array,
|
||||
]),
|
||||
validate: PropTypes.func,
|
||||
name: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
|
||||
@@ -13,8 +13,6 @@ const Text = (props) => {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
label,
|
||||
placeholder,
|
||||
@@ -36,9 +34,6 @@ const Text = (props) => {
|
||||
|
||||
const fieldType = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
});
|
||||
@@ -91,8 +86,6 @@ Text.defaultProps = {
|
||||
label: null,
|
||||
required: false,
|
||||
admin: {},
|
||||
defaultValue: undefined,
|
||||
initialData: undefined,
|
||||
placeholder: undefined,
|
||||
validate: text,
|
||||
path: '',
|
||||
@@ -105,8 +98,6 @@ Text.propTypes = {
|
||||
path: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
defaultValue: PropTypes.string,
|
||||
initialData: PropTypes.string,
|
||||
validate: PropTypes.func,
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
|
||||
@@ -13,14 +13,14 @@ const Textarea = (props) => {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
style,
|
||||
width,
|
||||
admin: {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
} = {},
|
||||
label,
|
||||
placeholder,
|
||||
readOnly,
|
||||
minLength,
|
||||
maxLength,
|
||||
rows,
|
||||
@@ -40,9 +40,6 @@ const Textarea = (props) => {
|
||||
errorMessage,
|
||||
} = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
});
|
||||
@@ -87,8 +84,6 @@ const Textarea = (props) => {
|
||||
Textarea.defaultProps = {
|
||||
required: false,
|
||||
label: null,
|
||||
defaultValue: undefined,
|
||||
initialData: undefined,
|
||||
validate: textarea,
|
||||
placeholder: null,
|
||||
path: '',
|
||||
@@ -102,8 +97,6 @@ Textarea.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
defaultValue: PropTypes.string,
|
||||
initialData: PropTypes.string,
|
||||
validate: PropTypes.func,
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
|
||||
@@ -26,8 +26,6 @@ const Upload = (props) => {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
admin: {
|
||||
readOnly,
|
||||
style,
|
||||
@@ -45,8 +43,6 @@ const Upload = (props) => {
|
||||
const addModalSlug = `${path}-add`;
|
||||
const selectExistingModalSlug = `${path}-select-existing`;
|
||||
|
||||
const dataToInitialize = (typeof initialData === 'object' && initialData.id) ? initialData.id : initialData;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
return validationResult;
|
||||
@@ -55,8 +51,6 @@ const Upload = (props) => {
|
||||
const fieldType = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData: dataToInitialize,
|
||||
defaultValue,
|
||||
validate: memoizedValidate,
|
||||
});
|
||||
|
||||
@@ -75,13 +69,9 @@ const Upload = (props) => {
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof initialData === 'object' && initialData?.id) {
|
||||
setInternalValue(initialData);
|
||||
}
|
||||
|
||||
if (typeof initialData === 'string') {
|
||||
if (typeof value === 'string') {
|
||||
const fetchFile = async () => {
|
||||
const response = await fetch(`${serverURL}${api}/${relationTo}/${initialData}`);
|
||||
const response = await fetch(`${serverURL}${api}/${relationTo}/${value}`);
|
||||
|
||||
if (response.ok) {
|
||||
const json = await response.json();
|
||||
@@ -91,7 +81,7 @@ const Upload = (props) => {
|
||||
|
||||
fetchFile();
|
||||
}
|
||||
}, [initialData, setInternalValue, relationTo]);
|
||||
}, [value, setInternalValue, relationTo]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -173,8 +163,6 @@ const Upload = (props) => {
|
||||
Upload.defaultProps = {
|
||||
label: null,
|
||||
required: false,
|
||||
defaultValue: undefined,
|
||||
initialData: undefined,
|
||||
admin: {},
|
||||
validate: upload,
|
||||
path: '',
|
||||
@@ -184,13 +172,6 @@ Upload.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
defaultValue: PropTypes.string,
|
||||
initialData: PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
}),
|
||||
PropTypes.string,
|
||||
]),
|
||||
validate: PropTypes.func,
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
export { default as auth } from './Auth';
|
||||
export { default as file } from './File';
|
||||
|
||||
export { default as email } from './Email';
|
||||
export { default as hidden } from './HiddenInput';
|
||||
export { default as text } from './Text';
|
||||
|
||||
@@ -2,59 +2,57 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const reducer = (currentState, action) => {
|
||||
const {
|
||||
type, index, moveToIndex, rows, data = [], initialRowData = {},
|
||||
type, rowIndex, moveFromIndex, moveToIndex, data, blockType,
|
||||
} = action;
|
||||
|
||||
const stateCopy = [...currentState];
|
||||
|
||||
switch (type) {
|
||||
case 'SET_ALL':
|
||||
return rows;
|
||||
case 'SET_ALL': {
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((dataRow) => {
|
||||
const row = {
|
||||
key: uuidv4(),
|
||||
open: true,
|
||||
};
|
||||
|
||||
if (dataRow.blockType) {
|
||||
row.blockType = dataRow.blockType;
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
case 'TOGGLE_COLLAPSE':
|
||||
stateCopy[index].open = !stateCopy[index].open;
|
||||
stateCopy[rowIndex].open = !stateCopy[rowIndex].open;
|
||||
return stateCopy;
|
||||
|
||||
case 'ADD': {
|
||||
stateCopy.splice(index + 1, 0, {
|
||||
const newRow = {
|
||||
open: true,
|
||||
key: uuidv4(),
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
data.splice(index + 1, 0, initialRowData);
|
||||
if (blockType) newRow.blockType = blockType;
|
||||
|
||||
const result = stateCopy.map((row, i) => {
|
||||
return {
|
||||
...row,
|
||||
data: {
|
||||
...(data[i] || {}),
|
||||
},
|
||||
};
|
||||
});
|
||||
stateCopy.splice(rowIndex + 1, 0, newRow);
|
||||
|
||||
return result;
|
||||
return stateCopy;
|
||||
}
|
||||
|
||||
|
||||
case 'REMOVE':
|
||||
stateCopy.splice(index, 1);
|
||||
stateCopy.splice(rowIndex, 1);
|
||||
return stateCopy;
|
||||
|
||||
case 'MOVE': {
|
||||
const stateCopyWithNewData = stateCopy.map((row, i) => {
|
||||
return {
|
||||
...row,
|
||||
data: {
|
||||
...(data[i] || {}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const movingRowState = { ...stateCopyWithNewData[index] };
|
||||
stateCopyWithNewData.splice(index, 1);
|
||||
stateCopyWithNewData.splice(moveToIndex, 0, movingRowState);
|
||||
return stateCopyWithNewData;
|
||||
const movingRowState = { ...stateCopy[moveFromIndex] };
|
||||
stateCopy.splice(moveFromIndex, 1);
|
||||
stateCopy.splice(moveToIndex, 0, movingRowState);
|
||||
return stateCopy;
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -3,43 +3,36 @@ import {
|
||||
} from 'react';
|
||||
import { useFormProcessing, useFormSubmitted, useFormModified, useForm } from '../Form/context';
|
||||
import useDebounce from '../../../hooks/useDebounce';
|
||||
import useUnmountEffect from '../../../hooks/useUnmountEffect';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const useFieldType = (options) => {
|
||||
const {
|
||||
path,
|
||||
initialData: data,
|
||||
defaultValue,
|
||||
validate,
|
||||
disableFormData,
|
||||
enableDebouncedValue,
|
||||
disableFormData,
|
||||
ignoreWhileFlattening,
|
||||
} = options;
|
||||
|
||||
// Determine what the initial data is to be used
|
||||
// If initialData is defined, that means that data has been provided
|
||||
// via the API and should override any default values present.
|
||||
// If no initialData, use default value
|
||||
const initialData = data !== undefined ? data : defaultValue;
|
||||
|
||||
const formContext = useForm();
|
||||
const submitted = useFormSubmitted();
|
||||
const processing = useFormProcessing();
|
||||
const modified = useFormModified();
|
||||
|
||||
const {
|
||||
dispatchFields, getField, setModified,
|
||||
dispatchFields, getField, setModified, reset,
|
||||
} = formContext;
|
||||
|
||||
const [internalValue, setInternalValue] = useState(initialData);
|
||||
const [internalValue, setInternalValue] = useState(undefined);
|
||||
|
||||
// Debounce internal values to update form state only every 60ms
|
||||
const debouncedValue = useDebounce(internalValue, 120);
|
||||
|
||||
// Get field by path
|
||||
const field = getField(path);
|
||||
const fieldExists = Boolean(field);
|
||||
|
||||
const initialValue = field?.initialValue;
|
||||
|
||||
// Valid could be a string equal to an error message
|
||||
const valid = (field && typeof field.valid === 'boolean') ? field.valid : true;
|
||||
@@ -57,48 +50,39 @@ const useFieldType = (options) => {
|
||||
fieldToDispatch.valid = false;
|
||||
}
|
||||
|
||||
if (disableFormData) {
|
||||
fieldToDispatch.disableFormData = true;
|
||||
}
|
||||
fieldToDispatch.disableFormData = disableFormData;
|
||||
fieldToDispatch.ignoreWhileFlattening = ignoreWhileFlattening;
|
||||
fieldToDispatch.initialValue = initialValue;
|
||||
|
||||
dispatchFields(fieldToDispatch);
|
||||
}, [path, dispatchFields, validate, disableFormData]);
|
||||
|
||||
}, [path, dispatchFields, validate, disableFormData, ignoreWhileFlattening, initialValue]);
|
||||
|
||||
// Method to return from `useFieldType`, used to
|
||||
// update internal field values from field component(s)
|
||||
// as fast as they arrive. NOTE - this method is NOT debounced
|
||||
const setValue = useCallback((e) => {
|
||||
const value = (e && e.target) ? e.target.value : e;
|
||||
const val = (e && e.target) ? e.target.value : e;
|
||||
|
||||
if (!modified) setModified(true);
|
||||
|
||||
setInternalValue(value);
|
||||
setInternalValue(val);
|
||||
}, [setModified, modified]);
|
||||
|
||||
// Remove field from state on "unmount"
|
||||
// This is mostly used for array / flex content row modifications
|
||||
useUnmountEffect(() => {
|
||||
formContext.dispatchFields({ path, type: 'REMOVE' });
|
||||
});
|
||||
useEffect(() => {
|
||||
setInternalValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
// The only time that the FORM value should be updated
|
||||
// is when the debounced value updates. So, when the debounced value updates,
|
||||
// send it up to the form
|
||||
|
||||
const formValue = enableDebouncedValue ? debouncedValue : internalValue;
|
||||
const valueToSend = enableDebouncedValue ? debouncedValue : internalValue;
|
||||
|
||||
useEffect(() => {
|
||||
if (!fieldExists || formValue !== undefined) {
|
||||
sendField(formValue);
|
||||
if (valueToSend !== undefined) {
|
||||
sendField(valueToSend);
|
||||
}
|
||||
}, [formValue, sendField, fieldExists]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData !== undefined) {
|
||||
setInternalValue(initialData);
|
||||
}
|
||||
}, [initialData]);
|
||||
}, [valueToSend, sendField]);
|
||||
|
||||
return {
|
||||
...options,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import format from 'date-fns/format';
|
||||
import config from 'payload/config';
|
||||
import Eyebrow from '../../elements/Eyebrow';
|
||||
import Form from '../../forms/Form';
|
||||
import PreviewButton from '../../elements/PreviewButton';
|
||||
@@ -13,13 +12,11 @@ import LeaveWithoutSaving from '../../modals/LeaveWithoutSaving';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const { serverURL, routes: { api } } = config;
|
||||
|
||||
const baseClass = 'global-edit';
|
||||
|
||||
const DefaultGlobalView = (props) => {
|
||||
const {
|
||||
global, data, onSave, permissions,
|
||||
global, data, onSave, permissions, action, apiURL,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
@@ -29,8 +26,6 @@ const DefaultGlobalView = (props) => {
|
||||
label,
|
||||
} = global;
|
||||
|
||||
const apiURL = `${serverURL}${api}/globals/${slug}`;
|
||||
const action = `${serverURL}${api}/globals/${slug}`;
|
||||
const hasSavePermission = permissions?.update?.permission;
|
||||
|
||||
return (
|
||||
@@ -57,7 +52,7 @@ const DefaultGlobalView = (props) => {
|
||||
operation="update"
|
||||
readOnly={!hasSavePermission}
|
||||
permissions={permissions.fields}
|
||||
filter={field => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
initialData={data}
|
||||
@@ -93,7 +88,7 @@ const DefaultGlobalView = (props) => {
|
||||
operation="update"
|
||||
readOnly={!hasSavePermission}
|
||||
permissions={permissions.fields}
|
||||
filter={field => field.position === 'sidebar'}
|
||||
filter={(field) => field.position === 'sidebar'}
|
||||
position="sidebar"
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
@@ -138,6 +133,8 @@ DefaultGlobalView.propTypes = {
|
||||
}),
|
||||
fields: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
action: PropTypes.string.isRequired,
|
||||
apiURL: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DefaultGlobalView;
|
||||
|
||||
@@ -5,6 +5,7 @@ import config from 'payload/config';
|
||||
import { useStepNav } from '../../elements/StepNav';
|
||||
import usePayloadAPI from '../../../hooks/usePayloadAPI';
|
||||
import { useUser } from '../../data/User';
|
||||
import { useLocale } from '../../utilities/Locale';
|
||||
|
||||
import RenderCustomComponent from '../../utilities/RenderCustomComponent';
|
||||
import DefaultGlobal from './Default';
|
||||
@@ -14,6 +15,7 @@ const { serverURL, routes: { admin, api } } = config;
|
||||
const GlobalView = (props) => {
|
||||
const { state: locationState } = useLocation();
|
||||
const history = useHistory();
|
||||
const locale = useLocale();
|
||||
const { setStepNav } = useStepNav();
|
||||
const { permissions } = useUser();
|
||||
|
||||
@@ -60,6 +62,8 @@ const GlobalView = (props) => {
|
||||
permissions: globalPermissions,
|
||||
global,
|
||||
onSave,
|
||||
apiURL: `${serverURL}${api}/globals/${slug}?depth=0`,
|
||||
action: `${serverURL}${api}/globals/${slug}?locale=${locale}`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Label from '../../Label';
|
||||
import Button from '../../../elements/Button';
|
||||
import CopyToClipboard from '../../../elements/CopyToClipboard';
|
||||
import { text } from '../../../../../fields/validations';
|
||||
import { useFormFields } from '../../Form/context';
|
||||
import useFieldType from '../../../../forms/useFieldType';
|
||||
import Label from '../../../../forms/Label';
|
||||
import Button from '../../../../elements/Button';
|
||||
import CopyToClipboard from '../../../../elements/CopyToClipboard';
|
||||
import { text } from '../../../../../../fields/validations';
|
||||
import { useFormFields } from '../../../../forms/Form/context';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -14,11 +13,7 @@ const path = 'apiKey';
|
||||
const baseClass = 'api-key';
|
||||
const validate = (val) => text(val, { minLength: 24, maxLength: 48 });
|
||||
|
||||
const APIKey = (props) => {
|
||||
const {
|
||||
initialData,
|
||||
} = props;
|
||||
|
||||
const APIKey = () => {
|
||||
const [initialAPIKey, setInitialAPIKey] = useState(null);
|
||||
|
||||
const { getField } = useFormFields();
|
||||
@@ -38,7 +33,6 @@ const APIKey = (props) => {
|
||||
|
||||
const fieldType = useFieldType({
|
||||
path: 'apiKey',
|
||||
initialData: initialData || initialAPIKey,
|
||||
validate,
|
||||
});
|
||||
|
||||
@@ -51,6 +45,12 @@ const APIKey = (props) => {
|
||||
setInitialAPIKey(uuidv4());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!apiKeyValue) {
|
||||
setValue(initialAPIKey);
|
||||
}
|
||||
}, [apiKeyValue, setValue, initialAPIKey]);
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
'api-key',
|
||||
@@ -83,12 +83,4 @@ const APIKey = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
APIKey.defaultProps = {
|
||||
initialData: undefined,
|
||||
};
|
||||
|
||||
APIKey.propTypes = {
|
||||
initialData: PropTypes.string,
|
||||
};
|
||||
|
||||
export default APIKey;
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Email from '../Email';
|
||||
import Password from '../Password';
|
||||
import Checkbox from '../Checkbox';
|
||||
import Button from '../../../elements/Button';
|
||||
import ConfirmPassword from '../ConfirmPassword';
|
||||
import { useFormFields } from '../../Form/context';
|
||||
import Email from '../../../../forms/field-types/Email';
|
||||
import Password from '../../../../forms/field-types/Password';
|
||||
import Checkbox from '../../../../forms/field-types/Checkbox';
|
||||
import Button from '../../../../elements/Button';
|
||||
import ConfirmPassword from '../../../../forms/field-types/ConfirmPassword';
|
||||
import { useFormFields, useFormModified } from '../../../../forms/Form/context';
|
||||
import APIKey from './APIKey';
|
||||
|
||||
import './index.scss';
|
||||
@@ -13,19 +13,25 @@ import './index.scss';
|
||||
const baseClass = 'auth-fields';
|
||||
|
||||
const Auth = (props) => {
|
||||
const { initialData, useAPIKey, requirePassword } = props;
|
||||
const { useAPIKey, requirePassword } = props;
|
||||
const [changingPassword, setChangingPassword] = useState(requirePassword);
|
||||
const { getField } = useFormFields();
|
||||
const modified = useFormModified();
|
||||
|
||||
const enableAPIKey = getField('enableAPIKey');
|
||||
|
||||
useEffect(() => {
|
||||
if (!modified) {
|
||||
setChangingPassword(false);
|
||||
}
|
||||
}, [modified]);
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<Email
|
||||
required
|
||||
name="email"
|
||||
label="Email"
|
||||
initialData={initialData?.email}
|
||||
autoComplete="email"
|
||||
/>
|
||||
{changingPassword && (
|
||||
@@ -60,12 +66,11 @@ const Auth = (props) => {
|
||||
{useAPIKey && (
|
||||
<div className={`${baseClass}__api-key`}>
|
||||
<Checkbox
|
||||
initialData={initialData?.enableAPIKey}
|
||||
label="Enable API Key"
|
||||
name="enableAPIKey"
|
||||
/>
|
||||
{enableAPIKey?.value && (
|
||||
<APIKey initialData={initialData?.apiKey} />
|
||||
<APIKey />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -74,18 +79,11 @@ const Auth = (props) => {
|
||||
};
|
||||
|
||||
Auth.defaultProps = {
|
||||
initialData: undefined,
|
||||
useAPIKey: false,
|
||||
requirePassword: false,
|
||||
};
|
||||
|
||||
Auth.propTypes = {
|
||||
fieldTypes: PropTypes.shape({}).isRequired,
|
||||
initialData: PropTypes.shape({
|
||||
enableAPIKey: PropTypes.bool,
|
||||
apiKey: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
}),
|
||||
useAPIKey: PropTypes.bool,
|
||||
requirePassword: PropTypes.bool,
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
@import '../shared.scss';
|
||||
@import '../../../../../scss/styles.scss';
|
||||
@import '../../../../forms/field-types/shared.scss';
|
||||
|
||||
.auth-fields {
|
||||
margin: base(1.5) 0 base(2);
|
||||
@@ -15,10 +15,12 @@ import DeleteDocument from '../../../elements/DeleteDocument';
|
||||
import * as fieldTypes from '../../../forms/field-types';
|
||||
import RenderTitle from '../../../elements/RenderTitle';
|
||||
import LeaveWithoutSaving from '../../../modals/LeaveWithoutSaving';
|
||||
import Auth from './Auth';
|
||||
import Upload from './Upload';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const { serverURL, routes: { api, admin } } = config;
|
||||
const { routes: { admin } } = config;
|
||||
|
||||
const baseClass = 'collection-edit';
|
||||
|
||||
@@ -26,7 +28,16 @@ const DefaultEditView = (props) => {
|
||||
const { params: { id } = {} } = useRouteMatch();
|
||||
|
||||
const {
|
||||
collection, isEditing, data, onSave, permissions, isLoading,
|
||||
collection,
|
||||
isEditing,
|
||||
data,
|
||||
onSave,
|
||||
permissions,
|
||||
isLoading,
|
||||
initialState,
|
||||
apiURL,
|
||||
action,
|
||||
hasSavePermission,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
@@ -38,16 +49,9 @@ const DefaultEditView = (props) => {
|
||||
timestamps,
|
||||
preview,
|
||||
auth,
|
||||
upload,
|
||||
} = collection;
|
||||
|
||||
const apiURL = `${serverURL}${api}/${slug}/${id}`;
|
||||
let action = `${serverURL}${api}/${slug}${isEditing ? `/${id}` : ''}`;
|
||||
const hasSavePermission = (isEditing && permissions?.update?.permission) || (!isEditing && permissions?.create?.permission);
|
||||
|
||||
if (auth && !isEditing) {
|
||||
action = `${action}/register`;
|
||||
}
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
isEditing && `${baseClass}--is-editing`,
|
||||
@@ -61,6 +65,7 @@ const DefaultEditView = (props) => {
|
||||
action={action}
|
||||
onSuccess={onSave}
|
||||
disabled={!hasSavePermission}
|
||||
initialState={initialState}
|
||||
>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Eyebrow />
|
||||
@@ -76,6 +81,19 @@ const DefaultEditView = (props) => {
|
||||
<RenderTitle {...{ data, useAsTitle, fallback: '[Untitled]' }} />
|
||||
</h1>
|
||||
</header>
|
||||
{auth && (
|
||||
<Auth
|
||||
useAPIKey={auth.useAPIKey}
|
||||
requirePassword={!isEditing}
|
||||
/>
|
||||
)}
|
||||
{upload && (
|
||||
<Upload
|
||||
data={data}
|
||||
{...upload}
|
||||
fieldTypes={fieldTypes}
|
||||
/>
|
||||
)}
|
||||
<RenderFields
|
||||
operation={isEditing ? 'update' : 'create'}
|
||||
readOnly={!hasSavePermission}
|
||||
@@ -83,7 +101,6 @@ const DefaultEditView = (props) => {
|
||||
filter={(field) => (!field?.admin?.position || (field?.admin?.position !== 'sidebar'))}
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
initialData={data}
|
||||
customComponentsPath={`${slug}.fields.`}
|
||||
/>
|
||||
</React.Fragment>
|
||||
@@ -145,7 +162,6 @@ const DefaultEditView = (props) => {
|
||||
position="sidebar"
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
initialData={data}
|
||||
customComponentsPath={`${slug}.fields.`}
|
||||
/>
|
||||
</div>
|
||||
@@ -187,9 +203,14 @@ DefaultEditView.defaultProps = {
|
||||
isEditing: false,
|
||||
isLoading: true,
|
||||
data: undefined,
|
||||
initialState: undefined,
|
||||
apiURL: undefined,
|
||||
};
|
||||
|
||||
DefaultEditView.propTypes = {
|
||||
hasSavePermission: PropTypes.bool.isRequired,
|
||||
action: PropTypes.string.isRequired,
|
||||
apiURL: PropTypes.string,
|
||||
isLoading: PropTypes.bool,
|
||||
collection: PropTypes.shape({
|
||||
labels: PropTypes.shape({
|
||||
@@ -203,7 +224,10 @@ DefaultEditView.propTypes = {
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
preview: PropTypes.func,
|
||||
timestamps: PropTypes.bool,
|
||||
auth: PropTypes.shape({}),
|
||||
auth: PropTypes.shape({
|
||||
useAPIKey: PropTypes.bool,
|
||||
}),
|
||||
upload: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
isEditing: PropTypes.bool,
|
||||
data: PropTypes.shape({
|
||||
@@ -223,6 +247,7 @@ DefaultEditView.propTypes = {
|
||||
}),
|
||||
fields: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
initialState: PropTypes.shape({}),
|
||||
};
|
||||
|
||||
export default DefaultEditView;
|
||||
|
||||
@@ -2,10 +2,10 @@ import React, {
|
||||
useState, useRef, useEffect, useCallback,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Button from '../../../elements/Button';
|
||||
import FileDetails from '../../../elements/FileDetails';
|
||||
import Error from '../../Error';
|
||||
import useFieldType from '../../../../forms/useFieldType';
|
||||
import Button from '../../../../elements/Button';
|
||||
import FileDetails from '../../../../elements/FileDetails';
|
||||
import Error from '../../../../forms/Error';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -34,10 +34,10 @@ const File = (props) => {
|
||||
const [replacingFile, setReplacingFile] = useState(false);
|
||||
|
||||
const {
|
||||
initialData = {}, adminThumbnail, staticURL,
|
||||
data = {}, adminThumbnail, staticURL,
|
||||
} = props;
|
||||
|
||||
const { filename } = initialData;
|
||||
const { filename } = data;
|
||||
|
||||
const {
|
||||
value,
|
||||
@@ -122,7 +122,7 @@ const File = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
setReplacingFile(false);
|
||||
}, [initialData]);
|
||||
}, [data]);
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
@@ -138,7 +138,7 @@ const File = (props) => {
|
||||
/>
|
||||
{(filename && !replacingFile) && (
|
||||
<FileDetails
|
||||
{...initialData}
|
||||
{...data}
|
||||
staticURL={staticURL}
|
||||
adminThumbnail={adminThumbnail}
|
||||
handleRemove={() => {
|
||||
@@ -167,7 +167,7 @@ const File = (props) => {
|
||||
</div>
|
||||
)}
|
||||
{!value && (
|
||||
<>
|
||||
<React.Fragment>
|
||||
<div
|
||||
className={`${baseClass}__drop-zone`}
|
||||
ref={dropRef}
|
||||
@@ -181,7 +181,7 @@ const File = (props) => {
|
||||
</Button>
|
||||
<span className={`${baseClass}__drag-label`}>or drag and drop a file here</span>
|
||||
</div>
|
||||
</>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<input
|
||||
ref={inputRef}
|
||||
@@ -195,13 +195,13 @@ const File = (props) => {
|
||||
};
|
||||
|
||||
File.defaultProps = {
|
||||
initialData: undefined,
|
||||
data: undefined,
|
||||
adminThumbnail: undefined,
|
||||
};
|
||||
|
||||
File.propTypes = {
|
||||
fieldTypes: PropTypes.shape({}).isRequired,
|
||||
initialData: PropTypes.shape({
|
||||
data: PropTypes.shape({
|
||||
filename: PropTypes.string,
|
||||
mimeType: PropTypes.string,
|
||||
filesize: PropTypes.number,
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
@import '../../../../../scss/styles.scss';
|
||||
|
||||
.file-field {
|
||||
position: relative;
|
||||
@@ -5,21 +5,14 @@ import config from 'payload/config';
|
||||
import { useStepNav } from '../../../elements/StepNav';
|
||||
import usePayloadAPI from '../../../../hooks/usePayloadAPI';
|
||||
import { useUser } from '../../../data/User';
|
||||
import formatFields from './formatFields';
|
||||
|
||||
import RenderCustomComponent from '../../../utilities/RenderCustomComponent';
|
||||
import DefaultEdit from './Default';
|
||||
import buildStateFromSchema from '../../../forms/Form/buildStateFromSchema';
|
||||
|
||||
const { serverURL, routes: { admin, api } } = config;
|
||||
|
||||
const EditView = (props) => {
|
||||
const { params: { id } = {} } = useRouteMatch();
|
||||
const { state: locationState } = useLocation();
|
||||
const history = useHistory();
|
||||
const { setStepNav } = useStepNav();
|
||||
const [fields, setFields] = useState([]);
|
||||
const { permissions } = useUser();
|
||||
|
||||
const { collection, isEditing } = props;
|
||||
|
||||
const {
|
||||
@@ -30,8 +23,17 @@ const EditView = (props) => {
|
||||
admin: {
|
||||
useAsTitle,
|
||||
},
|
||||
fields,
|
||||
auth,
|
||||
} = collection;
|
||||
|
||||
const { params: { id } = {} } = useRouteMatch();
|
||||
const { state: locationState } = useLocation();
|
||||
const history = useHistory();
|
||||
const { setStepNav } = useStepNav();
|
||||
const [initialState, setInitialState] = useState({});
|
||||
const { permissions } = useUser();
|
||||
|
||||
const onSave = (json) => {
|
||||
history.push(`${admin}/collections/${collection.slug}/${json?.doc?.id}`, {
|
||||
status: {
|
||||
@@ -69,11 +71,24 @@ const EditView = (props) => {
|
||||
}, [setStepNav, isEditing, pluralLabel, dataToRender, slug, useAsTitle]);
|
||||
|
||||
useEffect(() => {
|
||||
setFields(formatFields(collection, isEditing));
|
||||
}, [collection, isEditing]);
|
||||
const awaitInitialState = async () => {
|
||||
const state = await buildStateFromSchema(fields, dataToRender);
|
||||
setInitialState(state);
|
||||
};
|
||||
|
||||
awaitInitialState();
|
||||
}, [dataToRender, fields]);
|
||||
|
||||
const collectionPermissions = permissions?.[slug];
|
||||
|
||||
const apiURL = `${serverURL}${api}/${slug}/${id}`;
|
||||
let action = `${serverURL}${api}/${slug}${isEditing ? `/${id}` : ''}?depth=0`;
|
||||
const hasSavePermission = (isEditing && collectionPermissions?.update?.permission) || (!isEditing && collectionPermissions?.create?.permission);
|
||||
|
||||
if (auth && !isEditing) {
|
||||
action = `${action}/register`;
|
||||
}
|
||||
|
||||
return (
|
||||
<RenderCustomComponent
|
||||
DefaultComponent={DefaultEdit}
|
||||
@@ -81,10 +96,14 @@ const EditView = (props) => {
|
||||
componentProps={{
|
||||
isLoading,
|
||||
data: dataToRender,
|
||||
collection: { ...collection, fields },
|
||||
collection,
|
||||
permissions: collectionPermissions,
|
||||
isEditing,
|
||||
onSave,
|
||||
initialState,
|
||||
hasSavePermission,
|
||||
apiURL,
|
||||
action,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -106,6 +125,7 @@ EditView.propTypes = {
|
||||
}),
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
preview: PropTypes.func,
|
||||
auth: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
isEditing: PropTypes.bool,
|
||||
};
|
||||
|
||||
@@ -47,7 +47,9 @@ const DefaultList = (props) => {
|
||||
const [{ data }, { setParams }] = usePayloadAPI(apiURL, { initialParams: { depth: 0 } });
|
||||
|
||||
useEffect(() => {
|
||||
const params = {};
|
||||
const params = {
|
||||
depth: 0,
|
||||
};
|
||||
|
||||
if (page) params.page = page;
|
||||
if (sort) params.sort = sort;
|
||||
|
||||
Reference in New Issue
Block a user