mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-07-05 01:05:13 +08:00
fix: auto closing when close local image uploading (#2767)
This commit is contained in:
parent
9beefd7d5a
commit
b75d8ca621
@ -24,7 +24,7 @@ const WarningMask: FC<IWarningMaskProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={`${s.mask} absolute z-10 inset-0 pt-16`}
|
<div className={`${s.mask} absolute z-10 inset-0 pt-16`}
|
||||||
>
|
>
|
||||||
<div className='mx-auto w-[535px]'>
|
<div className='mx-auto px-10'>
|
||||||
<div className={`${s.icon} flex items-center justify-center w-11 h-11 rounded-xl bg-white`}>{warningIcon}</div>
|
<div className={`${s.icon} flex items-center justify-center w-11 h-11 rounded-xl bg-white`}>{warningIcon}</div>
|
||||||
<div className='mt-4 text-[24px] leading-normal font-semibold text-gray-800'>
|
<div className='mt-4 text-[24px] leading-normal font-semibold text-gray-800'>
|
||||||
{title}
|
{title}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import cn from 'classnames'
|
||||||
import Uploader from './uploader'
|
import Uploader from './uploader'
|
||||||
import ImageLinkInput from './image-link-input'
|
import ImageLinkInput from './image-link-input'
|
||||||
import { ImagePlus } from '@/app/components/base/icons/src/vender/line/images'
|
import { ImagePlus } from '@/app/components/base/icons/src/vender/line/images'
|
||||||
@ -25,16 +26,16 @@ const UploadOnlyFromLocal: FC<UploadOnlyFromLocalProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Uploader onUpload={onUpload} disabled={disabled} limit={limit}>
|
<Uploader onUpload={onUpload} disabled={disabled} limit={limit}>
|
||||||
{
|
{hovering => (
|
||||||
hovering => (
|
<div
|
||||||
<div className={`
|
className={`
|
||||||
relative flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer
|
relative flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer
|
||||||
${hovering && 'bg-gray-100'}
|
${hovering && 'bg-gray-100'}
|
||||||
`}>
|
`}
|
||||||
<ImagePlus className='w-4 h-4 text-gray-500' />
|
>
|
||||||
</div>
|
<ImagePlus className="w-4 h-4 text-gray-500" />
|
||||||
)
|
</div>
|
||||||
}
|
)}
|
||||||
</Uploader>
|
</Uploader>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -54,13 +55,16 @@ const UploaderButton: FC<UploaderButtonProps> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
const hasUploadFromLocal = methods.find(method => method === TransferMethod.local_file)
|
const hasUploadFromLocal = methods.find(
|
||||||
|
method => method === TransferMethod.local_file,
|
||||||
|
)
|
||||||
|
|
||||||
const handleUpload = (imageFile: ImageFile) => {
|
const handleUpload = (imageFile: ImageFile) => {
|
||||||
setOpen(false)
|
|
||||||
onUpload(imageFile)
|
onUpload(imageFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const closePopover = () => setOpen(false)
|
||||||
|
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
if (disabled)
|
if (disabled)
|
||||||
return
|
return
|
||||||
@ -72,43 +76,46 @@ const UploaderButton: FC<UploaderButtonProps> = ({
|
|||||||
<PortalToFollowElem
|
<PortalToFollowElem
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={setOpen}
|
onOpenChange={setOpen}
|
||||||
placement='top-start'
|
placement="top-start"
|
||||||
>
|
>
|
||||||
<PortalToFollowElemTrigger onClick={handleToggle}>
|
<PortalToFollowElemTrigger onClick={handleToggle}>
|
||||||
<div className={`
|
<button
|
||||||
relative flex items-center justify-center w-8 h-8 hover:bg-gray-100 rounded-lg
|
type="button"
|
||||||
${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}
|
disabled={disabled}
|
||||||
`}>
|
className="relative flex items-center justify-center w-8 h-8 enabled:hover:bg-gray-100 rounded-lg disabled:cursor-not-allowed"
|
||||||
<ImagePlus className='w-4 h-4 text-gray-500' />
|
>
|
||||||
</div>
|
<ImagePlus className="w-4 h-4 text-gray-500" />
|
||||||
|
</button>
|
||||||
</PortalToFollowElemTrigger>
|
</PortalToFollowElemTrigger>
|
||||||
<PortalToFollowElemContent className='z-50'>
|
<PortalToFollowElemContent className="z-50">
|
||||||
<div className='p-2 w-[260px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg'>
|
<div className="p-2 w-[260px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg">
|
||||||
<ImageLinkInput onUpload={handleUpload} />
|
<ImageLinkInput onUpload={handleUpload} />
|
||||||
{
|
{hasUploadFromLocal && (
|
||||||
hasUploadFromLocal && (
|
<>
|
||||||
<>
|
<div className="flex items-center mt-2 px-2 text-xs font-medium text-gray-400">
|
||||||
<div className='flex items-center mt-2 px-2 text-xs font-medium text-gray-400'>
|
<div className="mr-3 w-[93px] h-[1px] bg-gradient-to-l from-[#F3F4F6]" />
|
||||||
<div className='mr-3 w-[93px] h-[1px] bg-gradient-to-l from-[#F3F4F6]' />
|
OR
|
||||||
OR
|
<div className="ml-3 w-[93px] h-[1px] bg-gradient-to-r from-[#F3F4F6]" />
|
||||||
<div className='ml-3 w-[93px] h-[1px] bg-gradient-to-r from-[#F3F4F6]' />
|
</div>
|
||||||
</div>
|
<Uploader
|
||||||
<Uploader onUpload={handleUpload} limit={limit}>
|
onUpload={handleUpload}
|
||||||
{
|
limit={limit}
|
||||||
hovering => (
|
closePopover={closePopover}
|
||||||
<div className={`
|
>
|
||||||
flex items-center justify-center h-8 text-[13px] font-medium text-[#155EEF] rounded-lg cursor-pointer
|
{hovering => (
|
||||||
${hovering && 'bg-primary-50'}
|
<div
|
||||||
`}>
|
className={cn(
|
||||||
<Upload03 className='mr-1 w-4 h-4' />
|
'flex items-center justify-center h-8 text-[13px] font-medium text-[#155EEF] rounded-lg cursor-pointer',
|
||||||
{t('common.imageUploader.uploadFromComputer')}
|
hovering && 'bg-primary-50',
|
||||||
</div>
|
)}
|
||||||
)
|
>
|
||||||
}
|
<Upload03 className="mr-1 w-4 h-4" />
|
||||||
</Uploader>
|
{t('common.imageUploader.uploadFromComputer')}
|
||||||
</>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
</Uploader>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
@ -125,7 +132,9 @@ const ChatImageUploader: FC<ChatImageUploaderProps> = ({
|
|||||||
onUpload,
|
onUpload,
|
||||||
disabled,
|
disabled,
|
||||||
}) => {
|
}) => {
|
||||||
const onlyUploadLocal = settings.transfer_methods.length === 1 && settings.transfer_methods[0] === TransferMethod.local_file
|
const onlyUploadLocal
|
||||||
|
= settings.transfer_methods.length === 1
|
||||||
|
&& settings.transfer_methods[0] === TransferMethod.local_file
|
||||||
|
|
||||||
if (onlyUploadLocal) {
|
if (onlyUploadLocal) {
|
||||||
return (
|
return (
|
||||||
|
@ -30,6 +30,7 @@ const ImageLinkInput: FC<ImageLinkInputProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className='flex items-center pl-1.5 pr-1 h-8 border border-gray-200 bg-white shadow-xs rounded-lg'>
|
<div className='flex items-center pl-1.5 pr-1 h-8 border border-gray-200 bg-white shadow-xs rounded-lg'>
|
||||||
<input
|
<input
|
||||||
|
type="text"
|
||||||
className='grow mr-0.5 px-1 h-[18px] text-[13px] outline-none appearance-none'
|
className='grow mr-0.5 px-1 h-[18px] text-[13px] outline-none appearance-none'
|
||||||
value={imageLink}
|
value={imageLink}
|
||||||
onChange={e => setImageLink(e.target.value)}
|
onChange={e => setImageLink(e.target.value)}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Loading02, XClose } from '@/app/components/base/icons/src/vender/line/general'
|
import cn from 'classnames'
|
||||||
|
import {
|
||||||
|
Loading02,
|
||||||
|
XClose,
|
||||||
|
} from '@/app/components/base/icons/src/vender/line/general'
|
||||||
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
|
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||||
@ -30,7 +34,11 @@ const ImageList: FC<ImageListProps> = ({
|
|||||||
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
|
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
|
||||||
|
|
||||||
const handleImageLinkLoadSuccess = (item: ImageFile) => {
|
const handleImageLinkLoadSuccess = (item: ImageFile) => {
|
||||||
if (item.type === TransferMethod.remote_url && onImageLinkLoadSuccess && item.progress !== -1)
|
if (
|
||||||
|
item.type === TransferMethod.remote_url
|
||||||
|
&& onImageLinkLoadSuccess
|
||||||
|
&& item.progress !== -1
|
||||||
|
)
|
||||||
onImageLinkLoadSuccess(item._id)
|
onImageLinkLoadSuccess(item._id)
|
||||||
}
|
}
|
||||||
const handleImageLinkLoadError = (item: ImageFile) => {
|
const handleImageLinkLoadError = (item: ImageFile) => {
|
||||||
@ -39,89 +47,95 @@ const ImageList: FC<ImageListProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-wrap'>
|
<div className="flex flex-wrap">
|
||||||
{
|
{list.map(item => (
|
||||||
list.map(item => (
|
<div
|
||||||
<div
|
key={item._id}
|
||||||
key={item._id}
|
className="group relative mr-1 border-[0.5px] border-black/5 rounded-lg"
|
||||||
className='group relative mr-1 border-[0.5px] border-black/5 rounded-lg'
|
>
|
||||||
>
|
{item.type === TransferMethod.local_file && item.progress !== 100 && (
|
||||||
{
|
<>
|
||||||
item.type === TransferMethod.local_file && item.progress !== 100 && (
|
<div
|
||||||
<>
|
className="absolute inset-0 flex items-center justify-center z-[1] bg-black/30"
|
||||||
<div
|
style={{ left: item.progress > -1 ? `${item.progress}%` : 0 }}
|
||||||
className='absolute inset-0 flex items-center justify-center z-[1] bg-black/30'
|
>
|
||||||
style={{ left: item.progress > -1 ? `${item.progress}%` : 0 }}
|
{item.progress === -1 && (
|
||||||
>
|
<RefreshCcw01
|
||||||
{
|
className="w-5 h-5 text-white"
|
||||||
item.progress === -1 && (
|
onClick={() => onReUpload && onReUpload(item._id)}
|
||||||
<RefreshCcw01 className='w-5 h-5 text-white' onClick={() => onReUpload && onReUpload(item._id)} />
|
/>
|
||||||
)
|
)}
|
||||||
}
|
</div>
|
||||||
</div>
|
{item.progress > -1 && (
|
||||||
{
|
<span className="absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] text-sm text-white mix-blend-lighten z-[1]">
|
||||||
item.progress > -1 && (
|
{item.progress}%
|
||||||
<span className='absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] text-sm text-white mix-blend-lighten z-[1]'>{item.progress}%</span>
|
</span>
|
||||||
)
|
)}
|
||||||
}
|
</>
|
||||||
</>
|
)}
|
||||||
)
|
{item.type === TransferMethod.remote_url && item.progress !== 100 && (
|
||||||
}
|
<div
|
||||||
{
|
className={`
|
||||||
item.type === TransferMethod.remote_url && item.progress !== 100 && (
|
|
||||||
<div className={`
|
|
||||||
absolute inset-0 flex items-center justify-center rounded-lg z-[1] border
|
absolute inset-0 flex items-center justify-center rounded-lg z-[1] border
|
||||||
${item.progress === -1 ? 'bg-[#FEF0C7] border-[#DC6803]' : 'bg-black/[0.16] border-transparent'}
|
${
|
||||||
`}>
|
item.progress === -1
|
||||||
{
|
? 'bg-[#FEF0C7] border-[#DC6803]'
|
||||||
item.progress > -1 && (
|
: 'bg-black/[0.16] border-transparent'
|
||||||
<Loading02 className='animate-spin w-5 h-5 text-white' />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
item.progress === -1 && (
|
|
||||||
<TooltipPlus popupContent={t('common.imageUploader.pasteImageLinkInvalid')}>
|
|
||||||
<AlertTriangle className='w-4 h-4 text-[#DC6803]' />
|
|
||||||
</TooltipPlus>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
<img
|
`}
|
||||||
className='w-16 h-16 rounded-lg object-cover cursor-pointer border-[0.5px] border-black/5'
|
>
|
||||||
alt=''
|
{item.progress > -1 && (
|
||||||
onLoad={() => handleImageLinkLoadSuccess(item)}
|
<Loading02 className="animate-spin w-5 h-5 text-white" />
|
||||||
onError={() => handleImageLinkLoadError(item)}
|
)}
|
||||||
src={item.type === TransferMethod.remote_url ? item.url : item.base64Url}
|
{item.progress === -1 && (
|
||||||
onClick={() => item.progress === 100 && setImagePreviewUrl((item.type === TransferMethod.remote_url ? item.url : item.base64Url) as string)}
|
<TooltipPlus
|
||||||
/>
|
popupContent={t('common.imageUploader.pasteImageLinkInvalid')}
|
||||||
{
|
|
||||||
!readonly && (
|
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
absolute z-10 -top-[9px] -right-[9px] items-center justify-center w-[18px] h-[18px]
|
|
||||||
bg-white hover:bg-gray-50 border-[0.5px] border-black/[0.02] rounded-2xl shadow-lg
|
|
||||||
cursor-pointer
|
|
||||||
${item.progress === -1 ? 'flex' : 'hidden group-hover:flex'}
|
|
||||||
`}
|
|
||||||
onClick={() => onRemove && onRemove(item._id)}
|
|
||||||
>
|
>
|
||||||
<XClose className='w-3 h-3 text-gray-500' />
|
<AlertTriangle className="w-4 h-4 text-[#DC6803]" />
|
||||||
</div>
|
</TooltipPlus>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<img
|
||||||
|
className="w-16 h-16 rounded-lg object-cover cursor-pointer border-[0.5px] border-black/5"
|
||||||
|
alt={item.file?.name}
|
||||||
|
onLoad={() => handleImageLinkLoadSuccess(item)}
|
||||||
|
onError={() => handleImageLinkLoadError(item)}
|
||||||
|
src={
|
||||||
|
item.type === TransferMethod.remote_url
|
||||||
|
? item.url
|
||||||
|
: item.base64Url
|
||||||
|
}
|
||||||
|
onClick={() =>
|
||||||
|
item.progress === 100
|
||||||
|
&& setImagePreviewUrl(
|
||||||
|
(item.type === TransferMethod.remote_url
|
||||||
|
? item.url
|
||||||
|
: item.base64Url) as string,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
{
|
|
||||||
imagePreviewUrl && (
|
|
||||||
<ImagePreview
|
|
||||||
url={imagePreviewUrl}
|
|
||||||
onCancel={() => setImagePreviewUrl('')}
|
|
||||||
/>
|
/>
|
||||||
)
|
{!readonly && (
|
||||||
}
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cn(
|
||||||
|
'absolute z-10 -top-[9px] -right-[9px] items-center justify-center w-[18px] h-[18px]',
|
||||||
|
'bg-white hover:bg-gray-50 border-[0.5px] border-black/[0.02] rounded-2xl shadow-lg',
|
||||||
|
item.progress === -1 ? 'flex' : 'hidden group-hover:flex',
|
||||||
|
)}
|
||||||
|
onClick={() => onRemove && onRemove(item._id)}
|
||||||
|
>
|
||||||
|
<XClose className="w-3 h-3 text-gray-500" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{imagePreviewUrl && (
|
||||||
|
<ImagePreview
|
||||||
|
url={imagePreviewUrl}
|
||||||
|
onCancel={() => setImagePreviewUrl('')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { ALLOW_FILE_EXTENSIONS } from '@/types/app'
|
|||||||
type UploaderProps = {
|
type UploaderProps = {
|
||||||
children: (hovering: boolean) => JSX.Element
|
children: (hovering: boolean) => JSX.Element
|
||||||
onUpload: (imageFile: ImageFile) => void
|
onUpload: (imageFile: ImageFile) => void
|
||||||
|
closePopover?: () => void
|
||||||
limit?: number
|
limit?: number
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
@ -14,11 +15,16 @@ type UploaderProps = {
|
|||||||
const Uploader: FC<UploaderProps> = ({
|
const Uploader: FC<UploaderProps> = ({
|
||||||
children,
|
children,
|
||||||
onUpload,
|
onUpload,
|
||||||
|
closePopover,
|
||||||
limit,
|
limit,
|
||||||
disabled,
|
disabled,
|
||||||
}) => {
|
}) => {
|
||||||
const [hovering, setHovering] = useState(false)
|
const [hovering, setHovering] = useState(false)
|
||||||
const { handleLocalFileUpload } = useLocalFileUploader({ limit, onUpload, disabled })
|
const { handleLocalFileUpload } = useLocalFileUploader({
|
||||||
|
limit,
|
||||||
|
onUpload,
|
||||||
|
disabled,
|
||||||
|
})
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0]
|
const file = e.target.files?.[0]
|
||||||
@ -27,6 +33,7 @@ const Uploader: FC<UploaderProps> = ({
|
|||||||
return
|
return
|
||||||
|
|
||||||
handleLocalFileUpload(file)
|
handleLocalFileUpload(file)
|
||||||
|
closePopover?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -37,11 +44,8 @@ const Uploader: FC<UploaderProps> = ({
|
|||||||
>
|
>
|
||||||
{children(hovering)}
|
{children(hovering)}
|
||||||
<input
|
<input
|
||||||
className={`
|
className='absolute block inset-0 opacity-0 text-[0] w-full disabled:cursor-not-allowed cursor-pointer'
|
||||||
absolute block inset-0 opacity-0 text-[0] w-full
|
onClick={e => ((e.target as HTMLInputElement).value = '')}
|
||||||
${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}
|
|
||||||
`}
|
|
||||||
onClick={e => (e.target as HTMLInputElement).value = ''}
|
|
||||||
type='file'
|
type='file'
|
||||||
accept={ALLOW_FILE_EXTENSIONS.map(ext => `.${ext}`).join(',')}
|
accept={ALLOW_FILE_EXTENSIONS.map(ext => `.${ext}`).join(',')}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user