|
import { |
|
memo, |
|
useState, |
|
} from 'react' |
|
import { useTranslation } from 'react-i18next' |
|
import { RiUploadCloud2Line } from '@remixicon/react' |
|
import FileInput from '../file-input' |
|
import { useFile } from '../hooks' |
|
import { useStore } from '../store' |
|
import { FILE_URL_REGEX } from '../constants' |
|
import { |
|
PortalToFollowElem, |
|
PortalToFollowElemContent, |
|
PortalToFollowElemTrigger, |
|
} from '@/app/components/base/portal-to-follow-elem' |
|
import Button from '@/app/components/base/button' |
|
import type { FileUpload } from '@/app/components/base/features/types' |
|
import cn from '@/utils/classnames' |
|
|
|
type FileFromLinkOrLocalProps = { |
|
showFromLink?: boolean |
|
showFromLocal?: boolean |
|
trigger: (open: boolean) => React.ReactNode |
|
fileConfig: FileUpload |
|
} |
|
const FileFromLinkOrLocal = ({ |
|
showFromLink = true, |
|
showFromLocal = true, |
|
trigger, |
|
fileConfig, |
|
}: FileFromLinkOrLocalProps) => { |
|
const { t } = useTranslation() |
|
const files = useStore(s => s.files) |
|
const [open, setOpen] = useState(false) |
|
const [url, setUrl] = useState('') |
|
const [showError, setShowError] = useState(false) |
|
const { handleLoadFileFromLink } = useFile(fileConfig) |
|
const disabled = !!fileConfig.number_limits && files.length >= fileConfig.number_limits |
|
|
|
const handleSaveUrl = () => { |
|
if (!url) |
|
return |
|
|
|
if (!FILE_URL_REGEX.test(url)) { |
|
setShowError(true) |
|
return |
|
} |
|
handleLoadFileFromLink(url) |
|
setUrl('') |
|
} |
|
|
|
return ( |
|
<PortalToFollowElem |
|
placement='top' |
|
offset={4} |
|
open={open} |
|
onOpenChange={setOpen} |
|
> |
|
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)} asChild> |
|
{trigger(open)} |
|
</PortalToFollowElemTrigger> |
|
<PortalToFollowElemContent className='z-10'> |
|
<div className='p-3 w-[280px] bg-components-panel-bg-blur border-[0.5px] border-components-panel-border rounded-xl shadow-lg'> |
|
{ |
|
showFromLink && ( |
|
<> |
|
<div className={cn( |
|
'flex items-center p-1 h-8 bg-components-input-bg-active border border-components-input-border-active rounded-lg shadow-xs', |
|
showError && 'border-components-input-border-destructive', |
|
)}> |
|
<input |
|
className='grow block mr-0.5 px-1 bg-transparent system-sm-regular outline-none appearance-none' |
|
placeholder={t('common.fileUploader.pasteFileLinkInputPlaceholder') || ''} |
|
value={url} |
|
onChange={(e) => { |
|
setShowError(false) |
|
setUrl(e.target.value) |
|
}} |
|
disabled={disabled} |
|
/> |
|
<Button |
|
className='shrink-0' |
|
size='small' |
|
variant='primary' |
|
disabled={!url || disabled} |
|
onClick={handleSaveUrl} |
|
> |
|
{t('common.operation.ok')} |
|
</Button> |
|
</div> |
|
{ |
|
showError && ( |
|
<div className='mt-0.5 body-xs-regular text-text-destructive'> |
|
{t('common.fileUploader.pasteFileLinkInvalid')} |
|
</div> |
|
) |
|
} |
|
</> |
|
) |
|
} |
|
{ |
|
showFromLink && showFromLocal && ( |
|
<div className='flex items-center p-2 h-7 system-2xs-medium-uppercase text-text-quaternary'> |
|
<div className='mr-2 w-[93px] h-[1px] bg-gradient-to-l from-[rgba(16,24,40,0.08)]' /> |
|
OR |
|
<div className='ml-2 w-[93px] h-[1px] bg-gradient-to-r from-[rgba(16,24,40,0.08)]' /> |
|
</div> |
|
) |
|
} |
|
{ |
|
showFromLocal && ( |
|
<Button |
|
className='relative w-full' |
|
variant='secondary-accent' |
|
disabled={disabled} |
|
> |
|
<RiUploadCloud2Line className='mr-1 w-4 h-4' /> |
|
{t('common.fileUploader.uploadFromComputer')} |
|
<FileInput fileConfig={fileConfig} /> |
|
</Button> |
|
) |
|
} |
|
</div> |
|
</PortalToFollowElemContent> |
|
</PortalToFollowElem> |
|
) |
|
} |
|
|
|
export default memo(FileFromLinkOrLocal) |
|
|