feat: file drag and drop, multiple file select in media library (#783)

This commit is contained in:
Daniel Lautzenheiser
2023-05-08 12:32:05 -04:00
committed by GitHub
parent 4ff3967e18
commit e78ebbe65e
12 changed files with 744 additions and 262 deletions

View File

@ -13,17 +13,22 @@ import Button from '@staticcms/core/components/common/button/Button';
import Field from '@staticcms/core/components/common/field/Field';
import Image from '@staticcms/core/components/common/image/Image';
import Link from '@staticcms/core/components/common/link/Link';
import useDragHandlers from '@staticcms/core/lib/hooks/useDragHandlers';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
import useMediaPersist from '@staticcms/core/lib/hooks/useMediaPersist';
import useUUID from '@staticcms/core/lib/hooks/useUUID';
import { basename } from '@staticcms/core/lib/util';
import classNames from '@staticcms/core/lib/util/classNames.util';
import { KeyboardSensor, PointerSensor } from '@staticcms/core/lib/util/dnd.util';
import { isEmpty } from '@staticcms/core/lib/util/string.util';
import { selectConfig } from '@staticcms/core/reducers/selectors/config';
import { useAppSelector } from '@staticcms/core/store/hooks';
import SortableImage from './components/SortableImage';
import SortableLink from './components/SortableLink';
import type { DragEndEvent } from '@dnd-kit/core';
import type { FileOrImageField, MediaPath, WidgetControlProps } from '@staticcms/core/interface';
import type AssetProxy from '@staticcms/core/valueObjects/AssetProxy';
import type { FC, MouseEvent } from 'react';
const MAX_DISPLAY_LENGTH = 50;
@ -128,6 +133,41 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => {
handleOnChange,
);
const config = useAppSelector(selectConfig);
const handlePersistCallback = useCallback(
(_files: File[], assetProxies: (AssetProxy | null)[]) => {
const newPath =
assetProxies.length > 1 && allowsMultiple
? [
...(Array.isArray(internalValue) ? internalValue : [internalValue]),
...assetProxies.filter(f => f).map(f => f!.path),
]
: assetProxies[0]?.path;
if ((Array.isArray(newPath) && newPath.length === 0) || !newPath) {
return;
}
handleOnChange({
path: newPath,
});
},
[allowsMultiple, handleOnChange, internalValue],
);
/**
* Upload a file.
*/
const handlePersist = useMediaPersist({
mediaConfig: field.media_library ?? config?.media_library,
field,
callback: handlePersistCallback,
});
const { dragOverActive, handleDragEnter, handleDragLeave, handleDragOver, handleDrop } =
useDragHandlers(handlePersist);
const chooseUrl = useMemo(() => field.choose_url ?? false, [field.choose_url]);
const handleUrl = useCallback(
@ -417,20 +457,73 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => {
return useMemo(
() => (
<Field
inputRef={allowsMultiple ? undefined : uploadButtonRef}
label={label}
errors={errors}
noPadding={!hasErrors}
hint={field.hint}
forSingleList={forSingleList}
cursor={allowsMultiple ? 'default' : 'pointer'}
disabled={disabled}
<div
onDrop={handleDrop}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
className={classNames(
`
relative
border-2
transition-colors
`,
dragOverActive ? 'border-blue-500' : 'border-transparent',
)}
>
{content}
</Field>
<div className="-m-0.5">
<Field
inputRef={allowsMultiple ? undefined : uploadButtonRef}
label={label}
errors={errors}
noPadding={!hasErrors}
hint={field.hint}
forSingleList={forSingleList}
cursor={allowsMultiple ? 'default' : 'pointer'}
disabled={disabled}
>
{content}
</Field>
<div
className={classNames(
`
absolute
inset-0
flex
items-center
justify-center
pointer-events-none
font-bold
text-blue-500
bg-white/75
dark:text-blue-400
dark:bg-slate-800/75
transition-opacity
`,
dragOverActive ? 'opacity-100' : 'opacity-0',
)}
>
{t(`mediaLibrary.mediaLibraryModal.${forImage ? 'dropImages' : 'dropFiles'}`)}
</div>
</div>
</div>
),
[content, disabled, errors, field.hint, allowsMultiple, forSingleList, hasErrors, label],
[
handleDrop,
handleDragEnter,
handleDragLeave,
handleDragOver,
dragOverActive,
allowsMultiple,
label,
errors,
hasErrors,
field.hint,
forSingleList,
disabled,
content,
t,
],
);
},
);