Merge pull request #11024 from strapi/cm/edit-inputs

Cm/edit inputs
This commit is contained in:
cyril lopez 2021-09-21 15:24:46 +02:00 committed by GitHub
commit 1cb6275a16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 336 additions and 136 deletions

View File

@ -115,30 +115,6 @@
"component": "blog.test-como",
"required": false,
"min": 2
},
"temp_one_to_one": {
"type": "relation",
"relation": "oneToOne",
"target": "api::temp.temp",
"inversedBy": "one_to_one"
},
"temp": {
"type": "relation",
"relation": "manyToOne",
"target": "api::temp.temp",
"inversedBy": "one_to_many"
},
"temps": {
"type": "relation",
"relation": "oneToMany",
"target": "api::temp.temp",
"mappedBy": "many_to_one"
},
"many_to_many_temp": {
"type": "relation",
"relation": "manyToMany",
"target": "api::temp.temp",
"inversedBy": "many_to_many"
}
}
}

View File

@ -9,7 +9,7 @@
"description": ""
},
"options": {
"draftAndPublish": true
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
@ -17,7 +17,7 @@
"type": "string"
},
"long": {
"type": "string"
"type": "text"
},
"email": {
"type": "email"
@ -28,7 +28,7 @@
"password": {
"type": "password"
},
"number": {
"number_int": {
"type": "integer"
},
"enum": {
@ -77,39 +77,33 @@
"uid": {
"type": "uid"
},
"one_way": {
"type": "relation",
"relation": "oneToOne",
"target": "api::address.address"
"number_big": {
"type": "biginteger"
},
"one_to_one": {
"type": "relation",
"relation": "oneToOne",
"target": "api::address.address",
"inversedBy": "temp_one_to_one"
"number_dec": {
"type": "decimal"
},
"one_to_many": {
"type": "relation",
"relation": "oneToMany",
"target": "api::address.address",
"mappedBy": "temp"
"number_float": {
"type": "float"
},
"many_to_one": {
"type": "relation",
"relation": "manyToOne",
"target": "api::address.address",
"inversedBy": "temps"
"enum_req_def": {
"type": "enumeration",
"enum": [
"un",
"deux",
"trois"
],
"required": true,
"default": "un"
},
"many_to_many": {
"type": "relation",
"relation": "manyToMany",
"target": "api::address.address",
"inversedBy": "many_to_many_temp"
},
"many_way": {
"type": "relation",
"relation": "oneToMany",
"target": "api::address.address"
"enum_req": {
"type": "enumeration",
"enum": [
"un",
"deux",
"trois"
],
"required": true
}
}
}

View File

@ -93,6 +93,7 @@ const EditViewDataManagerProvider = ({
const errorsInForm = Object.keys(formErrors);
// TODO check if working with DZ, components...
// TODO use querySelector querySelectorAll('[data-strapi-field-error]')
if (errorsInForm.length > 0) {
const firstError = errorsInForm[0];
const el = document.getElementById(firstError);

View File

@ -1,4 +1,6 @@
import { get, isArray, isObject } from 'lodash';
import get from 'lodash/get';
import isArray from 'lodash/isArray';
import isObject from 'lodash/isObject';
/* eslint-disable indent */
@ -23,13 +25,23 @@ const cleanData = (retrievedData, currentSchema, componentsSchema) => {
}
break;
case 'date':
cleanedData =
value && value._isAMomentObject === true ? value.format('YYYY-MM-DD') : value;
break;
case 'datetime':
cleanedData = value && value._isAMomentObject === true ? value.toISOString() : value;
// TODO
// case 'date':
// cleanedData =
// value && value._isAMomentObject === true ? value.format('YYYY-MM-DD') : value;
// break;
// case 'datetime':
// cleanedData = value && value._isAMomentObject === true ? value.toISOString() : value;
// break;
case 'time': {
cleanedData = value;
if (value && value.split(':').length < 3) {
cleanedData = `${value}:00`;
}
break;
}
case 'media':
if (getOtherInfos(schema, [current, 'multiple']) === true) {
cleanedData = value ? value.filter(file => !(file instanceof File)) : null;

View File

@ -1,16 +1,26 @@
/**
*
* Input
* GenericInput
* This is a temp file move it to the helper plugin when ready
*/
import React from 'react';
import { useIntl } from 'react-intl';
import { ToggleInput } from '@strapi/parts/ToggleInput';
import { TextInput } from '@strapi/parts/TextInput';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import cloneDeep from 'lodash/cloneDeep';
import { formatISO } from 'date-fns';
import { DatePicker } from '@strapi/parts/DatePicker';
import { NumberInput } from '@strapi/parts/NumberInput';
import { Select, Option } from '@strapi/parts/Select';
import { Textarea } from '@strapi/parts/Textarea';
import { TextInput } from '@strapi/parts/TextInput';
import { TimePicker } from '@strapi/parts/TimePicker';
import { ToggleInput } from '@strapi/parts/ToggleInput';
import Hide from '@strapi/icons/Hide';
import Show from '@strapi/icons/Show';
const Input = ({
const GenericInput = ({
autoComplete,
customInputs,
description,
disabled,
@ -19,12 +29,15 @@ const Input = ({
error,
name,
onChange,
options,
placeholder,
step,
type,
value,
...rest
}) => {
const { formatMessage } = useIntl();
const [showPassword, setShowPassword] = useState(false);
const CustomInput = customInputs ? customInputs[type] : null;
@ -46,10 +59,12 @@ const Input = ({
);
}
const label = formatMessage(
{ id: intlLabel.id, defaultMessage: intlLabel.defaultMessage },
{ ...intlLabel.values }
);
const label = intlLabel.id
? formatMessage(
{ id: intlLabel.id, defaultMessage: intlLabel.defaultMessage },
{ ...intlLabel.values }
)
: name;
const hint = description
? formatMessage(
@ -58,30 +73,6 @@ const Input = ({
)
: '';
if (type === 'bool') {
return (
<ToggleInput
checked={value || false}
disabled={disabled}
hint={hint}
label={label}
labelAction={labelAction}
name={name}
offLabel={formatMessage({
id: 'app.components.ToggleCheckbox.off-label',
defaultMessage: 'Off',
})}
onLabel={formatMessage({
id: 'app.components.ToggleCheckbox.on-label',
defaultMessage: 'On',
})}
onChange={e => {
onChange({ target: { name, value: e.target.checked } });
}}
/>
);
}
const formattedPlaceholder = placeholder
? formatMessage(
{ id: placeholder.id, defaultMessage: placeholder.defaultMessage },
@ -91,34 +82,228 @@ const Input = ({
const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : '';
return (
<TextInput
disabled={disabled}
error={errorMessage}
label={label}
labelAction={labelAction}
id={name}
hint={hint}
name={name}
onChange={onChange}
placeholder={formattedPlaceholder}
type={type}
value={value || ''}
/>
);
switch (type) {
case 'bool': {
return (
<ToggleInput
checked={value || false}
disabled={disabled}
hint={hint}
label={label}
labelAction={labelAction}
name={name}
offLabel={formatMessage({
id: 'app.components.ToggleCheckbox.off-label',
defaultMessage: 'Off',
})}
onLabel={formatMessage({
id: 'app.components.ToggleCheckbox.on-label',
defaultMessage: 'On',
})}
onChange={e => {
onChange({ target: { name, value: e.target.checked } });
}}
/>
);
}
case 'date': {
return (
<DatePicker
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
disabled={disabled}
error={errorMessage}
label={label}
labelAction={labelAction}
id={name}
hint={hint}
name={name}
onChange={date => {
const formattedDate = formatISO(cloneDeep(date), { representation: 'date' });
onChange({ target: { name, value: formattedDate, type } });
}}
onClear={() => onChange({ target: { name, value: '', type } })}
placeholder={formattedPlaceholder}
selectedDate={value ? new Date(value) : null}
selectedDateLabel={formattedDate => `Date picker, current is ${formattedDate}`}
/>
);
}
case 'number': {
return (
<NumberInput
disabled={disabled}
error={errorMessage}
label={label}
labelAction={labelAction}
id={name}
hint={hint}
name={name}
onValueChange={value => {
onChange({ target: { name, value, type } });
}}
placeholder={formattedPlaceholder}
step={step}
value={value || undefined}
/>
);
}
case 'email':
case 'text':
case 'string': {
return (
<TextInput
autoComplete={autoComplete}
disabled={disabled}
error={errorMessage}
label={label}
labelAction={labelAction}
id={name}
hint={hint}
name={name}
onChange={onChange}
placeholder={formattedPlaceholder}
type={type}
value={value || ''}
/>
);
}
case 'password': {
return (
<TextInput
autoComplete={autoComplete}
disabled={disabled}
error={errorMessage}
endAction={
<button
aria-label={formatMessage({
id: 'Auth.form.password.show-password',
defaultMessage: 'Show password',
})}
onClick={() => {
setShowPassword(prev => !prev);
}}
style={{
border: 'none',
padding: 0,
background: 'transparent',
}}
type="button"
>
{showPassword ? <Show /> : <Hide />}
</button>
}
label={label}
labelAction={labelAction}
id={name}
hint={hint}
name={name}
onChange={onChange}
placeholder={formattedPlaceholder}
type={showPassword ? 'text' : 'password'}
value={value || ''}
/>
);
}
case 'select': {
return (
<Select
disabled={disabled}
error={errorMessage}
label={label}
labelAction={labelAction}
id={name}
hint={hint}
name={name}
onChange={value => {
onChange({ target: { name, value: value === '' ? null : value, type: 'select' } });
}}
placeholder={formattedPlaceholder}
value={value || ''}
>
{options.map(({ metadatas: { intlLabel, disabled, hidden }, key, value }) => {
return (
<Option key={key} value={value} disabled={disabled} hidden={hidden}>
{formatMessage(intlLabel)}
</Option>
);
})}
</Select>
);
}
case 'textarea': {
return (
<Textarea
disabled={disabled}
error={errorMessage}
label={label}
labelAction={labelAction}
id={name}
hint={hint}
name={name}
onChange={onChange}
placeholder={formattedPlaceholder}
type={type}
value={value || ''}
>
{value}
</Textarea>
);
}
case 'time': {
let time = value;
// The backend send a value which has the following format: '00:45:00.000'
// or the time picker only supports hours & minutes so we need to mutate the value
if (value && value.split(':').length > 2) {
time = time.split(':');
time.pop();
time = time.join(':');
}
return (
<TimePicker
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
disabled={disabled}
error={errorMessage}
label={label}
labelAction={labelAction}
id={name}
hint={hint}
name={name}
onChange={time => {
onChange({ target: { name, value: `${time}`, type } });
}}
onClear={() => {
onChange({ target: { name, value: null, type } });
}}
placeholder={formattedPlaceholder}
step={step}
value={time}
/>
);
}
default: {
return <div>{type} is not supported</div>;
}
}
};
Input.defaultProps = {
GenericInput.defaultProps = {
autoComplete: undefined,
customInputs: null,
description: null,
disabled: false,
error: '',
labelAction: undefined,
placeholder: null,
options: [],
step: 1,
value: '',
};
Input.propTypes = {
GenericInput.propTypes = {
autoComplete: PropTypes.string,
customInputs: PropTypes.object,
description: PropTypes.shape({
id: PropTypes.string.isRequired,
@ -135,13 +320,28 @@ Input.propTypes = {
labelAction: PropTypes.element,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
options: PropTypes.arrayOf(
PropTypes.shape({
metadatas: PropTypes.shape({
intlLabel: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
}).isRequired,
disabled: PropTypes.bool,
hidden: PropTypes.bool,
}).isRequired,
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
}).isRequired
),
placeholder: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
step: PropTypes.number,
type: PropTypes.string.isRequired,
value: PropTypes.any,
};
export default Input;
export default GenericInput;

View File

@ -166,7 +166,7 @@ function Inputs({
isRequired,
]);
const { label, description, visible } = metadatas;
const { label, description, placeholder, visible } = metadatas;
if (visible === false) {
return null;
@ -218,8 +218,10 @@ function Inputs({
// wysiwyg: WysiwygWithErrors,
// uid: InputUID,
// ...fields,
json: () => <div>TODO json</div>,
media: () => <div>TODO media</div>,
uid: () => <div>TODO uid</div>,
wysiwyg: () => <div>TODO wysiwyg</div>,
}}
multiple={fieldSchema.multiple || false}
attribute={fieldSchema}
@ -227,9 +229,10 @@ function Inputs({
onBlur={onBlur}
onChange={onChange}
options={options}
placeholder={placeholder ? { id: placeholder, defaultMessage: placeholder } : null}
step={step}
type={inputType}
validations={validations}
// validations={validations}
value={inputValue}
withDefaultValue={false}
/>

View File

@ -1,19 +1,32 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
const generateOptions = (options, isRequired = false) => [
<FormattedMessage id="components.InputSelect.option.placeholder" key="__enum_option_null">
{msg => (
<option disabled={isRequired} hidden={isRequired} value="">
{msg}
</option>
)}
</FormattedMessage>,
...options.map(v => (
<option key={v} value={v}>
{v}
</option>
)),
];
const generateOptions = (options, isRequired = false) => {
return [
{
metadatas: {
intlLabel: {
id: 'components.InputSelect.option.placeholder',
defaultMessage: 'Choose here',
},
disabled: isRequired,
hidden: isRequired,
},
key: '__enum_option_null',
value: '',
},
...options.map(option => {
return {
metadatas: {
intlLabel: {
id: option,
defaultMessage: option,
},
hidden: false,
disabled: false,
},
key: option,
value: option,
};
}),
];
};
export default generateOptions;

View File

@ -156,7 +156,7 @@ const EditView = ({
{grid.map(
({ fieldSchema, labelAction, metadatas, name, size }) => {
return (
<GridItem col={size} key={name}>
<GridItem col={size} key={name} s={12} xs={12}>
<Inputs
fieldSchema={fieldSchema}
keys={name}

View File

@ -7,7 +7,7 @@ import { TimePicker } from '@strapi/parts/TimePicker';
import { Select, Option } from '@strapi/parts/Select';
import { useIntl } from 'react-intl';
import { formatISO } from 'date-fns';
import { cloneDeep } from 'lodash';
import cloneDeep from 'lodash/cloneDeep';
const Inputs = ({ label, onChange, options, type, value }) => {
const { formatMessage } = useIntl();

View File

@ -110,6 +110,7 @@ const CMEditViewLocalePicker = ({
<Option
value={value.value}
disabled
hidden
startIcon={hasDraftAndPublishEnabled ? <Bullet status={currentLocaleStatus} /> : null}
>
{value.label}