feat: abstracts upload component

This commit is contained in:
Jacob Fletcher
2021-11-29 16:34:39 -05:00
parent fa671378c7
commit f234f68019
5 changed files with 237 additions and 146 deletions

View File

@@ -0,0 +1,173 @@
import React, { useEffect, useState } from 'react';
import { useModal } from '@faceless-ui/modal';
import Button from '../../../elements/Button';
import Label from '../../Label';
import Error from '../../Error';
import FileDetails from '../../../elements/FileDetails';
import FieldDescription from '../../FieldDescription';
import { UploadField } from '../../../../../fields/config/types';
import { Description } from '../../FieldDescription/types';
import { FieldTypes } from '..';
import AddModal from './Add';
import SelectExistingModal from './SelectExisting';
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
import './index.scss';
const baseClass = 'upload';
export type UploadInputProps = Omit<UploadField, 'type'> & {
showError: boolean
errorMessage?: string
readOnly?: boolean
path?: string
required?: boolean
value?: string
description?: Description
onChange?: (e) => void
placeholder?: string
style?: React.CSSProperties
width?: string
fieldTypes?: FieldTypes
collection?: SanitizedCollectionConfig
serverURL?: string
api?: string
}
const UploadInput: React.FC<UploadInputProps> = (props) => {
const {
path,
required,
readOnly,
style,
width,
description,
label,
relationTo,
fieldTypes,
value,
onChange,
showError,
serverURL = 'http://localhost:3000',
api = '/api',
collection,
errorMessage,
} = props;
const { toggle } = useModal();
const addModalSlug = `${path}-add`;
const selectExistingModalSlug = `${path}-select-existing`;
const [file, setFile] = useState(undefined);
const [missingFile, setMissingFile] = useState(false);
const classes = [
'field-type',
baseClass,
showError && 'error',
readOnly && 'read-only',
].filter(Boolean).join(' ');
useEffect(() => {
if (typeof value === 'string' && value !== '') {
const fetchFile = async () => {
const response = await fetch(`${serverURL}${api}/${relationTo}/${value}`);
if (response.ok) {
const json = await response.json();
setFile(json);
} else {
setMissingFile(true);
setFile(undefined);
}
};
fetchFile();
} else {
setFile(undefined);
}
}, [
value,
relationTo,
api,
serverURL,
]);
return (
<div
className={classes}
style={{
...style,
width,
}}
>
<Error
showError={showError}
message={errorMessage}
/>
<Label
htmlFor={path}
label={label}
required={required}
/>
{collection?.upload && (
<React.Fragment>
{(file && !missingFile) && (
<FileDetails
collection={collection}
doc={file}
handleRemove={() => {
onChange(null);
}}
/>
)}
{(!file || missingFile) && (
<div className={`${baseClass}__wrap`}>
<Button
buttonStyle="secondary"
onClick={() => {
toggle(addModalSlug);
}}
>
Upload new
{' '}
{collection.labels.singular}
</Button>
<Button
buttonStyle="secondary"
onClick={() => {
toggle(selectExistingModalSlug);
}}
>
Choose from existing
</Button>
</div>
)}
<AddModal
{...{
collection,
slug: addModalSlug,
fieldTypes,
setValue: onChange,
}}
/>
<SelectExistingModal
{...{
collection,
slug: selectExistingModalSlug,
setValue: onChange,
addModalSlug,
}}
/>
<FieldDescription
value={file}
description={description}
/>
</React.Fragment>
)}
</div>
);
};
export default UploadInput;

View File

@@ -1,27 +1,21 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useModal } from '@faceless-ui/modal';
import React, { useCallback } from 'react';
import { useConfig } from '@payloadcms/config-provider';
import useField from '../../useField';
import withCondition from '../../withCondition';
import Button from '../../../elements/Button';
import Label from '../../Label';
import Error from '../../Error';
import { upload } from '../../../../../fields/validations';
import FileDetails from '../../../elements/FileDetails';
import AddModal from './Add';
import SelectExistingModal from './SelectExisting';
import { Props } from './types';
import UploadInput from './Input';
import './index.scss';
import FieldDescription from '../../FieldDescription';
const baseClass = 'upload';
const Upload: React.FC<Props> = (props) => {
const { toggle } = useModal();
const [internalValue, setInternalValue] = useState(undefined);
const [missingFile, setMissingFile] = useState(false);
const { collections, serverURL, routes: { api } } = useConfig();
const {
collections,
serverURL,
routes: {
api,
},
} = useConfig();
const {
path: pathFromProps,
@@ -38,15 +32,11 @@ const Upload: React.FC<Props> = (props) => {
validate = upload,
relationTo,
fieldTypes,
value: valueFromProps,
onChange: onChangeFromProps,
} = props;
const collection = collections.find((coll) => coll.slug === relationTo);
const path = pathFromProps || name;
const addModalSlug = `${path}-add`;
const selectExistingModalSlug = `${path}-select-existing`;
const memoizedValidate = useCallback((value) => {
const validationResult = validate(value, { required });
@@ -60,132 +50,42 @@ const Upload: React.FC<Props> = (props) => {
});
const {
value: valueFromContext,
value,
showError,
setValue,
errorMessage,
} = fieldType;
const value = valueFromProps || valueFromContext || '';
const classes = [
'field-type',
baseClass,
showError && 'error',
readOnly && 'read-only',
].filter(Boolean).join(' ');
useEffect(() => {
if (typeof value === 'string') {
const fetchFile = async () => {
const response = await fetch(`${serverURL}${api}/${relationTo}/${value}`);
if (response.ok) {
const json = await response.json();
setInternalValue(json);
} else {
setInternalValue(undefined);
setMissingFile(true);
}
};
fetchFile();
}
const onChange = useCallback((incomingValue) => {
const incomingID = incomingValue?.id || incomingValue;
setValue(incomingID);
}, [
value,
relationTo,
api,
serverURL,
setValue
setValue,
]);
useEffect(() => {
const { id: incomingID } = internalValue || {};
if (typeof onChangeFromProps === 'function') {
onChangeFromProps(incomingID)
} else {
setValue(incomingID);
}
}, [internalValue]);
return (
<div
className={classes}
style={{
...style,
width,
}}
>
<Error
showError={showError}
message={errorMessage}
/>
<Label
htmlFor={path}
if (collection.upload) {
return (
<UploadInput
value={value as string}
onChange={onChange}
description={description}
label={label}
required={required}
showError={showError}
serverURL={serverURL}
api={api}
errorMessage={errorMessage}
readOnly={readOnly}
style={style}
width={width}
collection={collection}
fieldTypes={fieldTypes}
name={name}
relationTo={relationTo}
/>
{collection?.upload && (
<React.Fragment>
{(internalValue && !missingFile) && (
<FileDetails
collection={collection}
doc={internalValue}
handleRemove={() => {
setInternalValue(undefined);
setValue(null);
}}
/>
)}
{(!value || missingFile) && (
<div className={`${baseClass}__wrap`}>
<Button
buttonStyle="secondary"
onClick={() => {
toggle(addModalSlug);
}}
>
Upload new
{' '}
{collection.labels.singular}
</Button>
<Button
buttonStyle="secondary"
onClick={() => {
toggle(selectExistingModalSlug);
}}
>
Choose from existing
</Button>
</div>
)}
<AddModal {...{
collection,
slug: addModalSlug,
fieldTypes,
setValue: (val) => {
setMissingFile(false);
setInternalValue(val);
},
}}
/>
<SelectExistingModal {...{
collection,
slug: selectExistingModalSlug,
setValue: (val) => {
setMissingFile(false);
setInternalValue(val);
},
addModalSlug,
}}
/>
<FieldDescription
value={value}
description={description}
/>
</React.Fragment>
)}
</div>
);
);
}
return null;
};
export default withCondition(Upload);

View File

@@ -113,6 +113,7 @@ const useField = <T extends unknown>(options: Options): FieldType<T> => {
sendField(valueToSend);
}
}, [
path,
valueToSend,
sendField,
field,

View File

@@ -167,8 +167,6 @@ export type UploadField = FieldBase & {
type: 'upload'
relationTo: string
maxDepth?: number
value?: string
onChange?: (value: string) => void
}
type CodeAdmin = Admin & {