import React, { memo, useCallback, useMemo, useRef, useState } from 'react'; 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 useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert'; import useUUID from '@staticcms/core/lib/hooks/useUUID'; import { basename } from '@staticcms/core/lib/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 type { BaseField, Collection, FileOrImageField, MediaPath, WidgetControlProps, } from '@staticcms/core/interface'; import type { FC, MouseEvent } from 'react'; const MAX_DISPLAY_LENGTH = 50; function isMultiple(value: string | string[] | null | undefined): value is string[] { return Array.isArray(value); } export function getValidFileValue(value: string | string[] | null | undefined) { if (value) { return isMultiple(value) ? value.map(v => basename(v)) : basename(value); } return value; } export interface WithFileControlProps { forImage?: boolean; } const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => { const FileControl: FC> = memo( ({ value, label, collection, field, errors, forSingleList, duplicate, onChange, openMediaLibrary, hasErrors, disabled, t, }) => { const controlID = useUUID(); const [internalRawValue, setInternalValue] = useState(value ?? ''); const internalValue = useMemo( () => (duplicate ? value ?? '' : internalRawValue), [internalRawValue, duplicate, value], ); const uploadButtonRef = useRef(null); const handleOnChange = useCallback( ({ path: newValue }: MediaPath) => { if (newValue !== internalValue) { setInternalValue(newValue); setTimeout(() => { onChange(newValue); }); } }, [internalValue, onChange], ); const handleOpenMediaLibrary = useMediaInsert( { path: internalValue }, { collection, field, controlID, forImage }, handleOnChange, ); const config = useAppSelector(selectConfig); const allowsMultiple = useMemo(() => { return field.multiple ?? false; }, [field.multiple]); const chooseUrl = useMemo(() => field.choose_url ?? false, [field.choose_url]); const handleUrl = useCallback( (subject: 'image' | 'file') => (e: MouseEvent) => { e.preventDefault(); const url = window.prompt(t(`editor.editorWidgets.${subject}.promptUrl`)); handleOnChange({ path: url ?? '' }); }, [handleOnChange, t], ); const handleRemove = useCallback( (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); handleOnChange({ path: '' }); }, [handleOnChange], ); const onRemoveOne = useCallback( (index: number) => () => { if (Array.isArray(internalValue)) { const newValue = [...internalValue]; newValue.splice(index, 1); handleOnChange({ path: newValue }); } }, [handleOnChange, internalValue], ); const onReplaceOne = useCallback( (index: number) => () => { return openMediaLibrary({ controlID, forImage, value: internalValue, replaceIndex: index, allowMultiple: false, config: config?.media_library, collection: collection as Collection, field, }); }, [openMediaLibrary, controlID, internalValue, config, collection, field], ); // TODO Readd when multiple uploads is supported // const onSortEnd = useCallback( // ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => { // if (Array.isArray(internalValue)) { // const newValue = arrayMoveImmutable(internalValue, oldIndex, newIndex); // handleOnChange(newValue); // } // }, // [handleOnChange, internalValue], // ); const renderFileLink = useCallback( (link: string | undefined | null) => { if (!link) { return null; } const text = link.length <= MAX_DISPLAY_LENGTH ? link : `${link.slice(0, MAX_DISPLAY_LENGTH / 2)}\u2026${link.slice( -(MAX_DISPLAY_LENGTH / 2) + 1, )}`; return ( {text} ); }, [collection, field], ); const renderedImagesLinks = useMemo(() => { if (forImage) { if (!internalValue) { return null; } if (isMultiple(internalValue)) { return (
{internalValue.map((itemValue, index) => ( ))}
); } return (
); } if (isMultiple(internalValue)) { return (
    {internalValue.map(val => (
  • {renderFileLink(val)}
  • ))}
); } return
{renderFileLink(internalValue)}
; }, [collection, field, internalValue, onRemoveOne, onReplaceOne, renderFileLink]); const content: JSX.Element = useMemo(() => { const subject = forImage ? 'image' : 'file'; if (Array.isArray(internalValue) ? internalValue.length === 0 : isEmpty(internalValue)) { return (
{chooseUrl ? ( ) : null}
); } return (
{renderedImagesLinks}
{chooseUrl && !allowsMultiple ? ( ) : null}
); }, [ internalValue, renderedImagesLinks, handleOpenMediaLibrary, disabled, t, allowsMultiple, chooseUrl, handleUrl, handleRemove, ]); return useMemo( () => ( {content} ), [content, disabled, errors, field.hint, forSingleList, hasErrors, label], ); }, ); FileControl.displayName = 'FileControl'; return FileControl; }; export default withFileControl;