mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
fix(web): align document drop state (WTA-2106)
Linear: https://linear.app/dify/issue/WTA-2106
This commit is contained in:
parent
91006dfd2e
commit
ac5ba51ec0
@ -1773,7 +1773,7 @@ describe('DocumentsPage', () => {
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('opens the upload form when files are dropped on a populated document page', () => {
|
||||
it('shows the drop target while dragging and previews dropped files in the upload form', () => {
|
||||
documentsQuery.data = { pages: [{ items: [document()] }] }
|
||||
const droppedFile = new File(['# handbook'], 'handbook.md', { type: 'text/markdown' })
|
||||
|
||||
@ -1783,6 +1783,21 @@ describe('DocumentsPage', () => {
|
||||
.getByRole('heading', { name: 'dataset.newKnowledge.documents' })
|
||||
.closest('section')
|
||||
expect(documentSurface).not.toBeNull()
|
||||
fireEvent.dragEnter(documentSurface!, {
|
||||
dataTransfer: { files: [droppedFile], types: ['Files'] },
|
||||
})
|
||||
|
||||
expect(screen.getByText('dataset.newKnowledge.dropFilesHere')).toBeInTheDocument()
|
||||
|
||||
fireEvent.dragLeave(documentSurface!, {
|
||||
dataTransfer: { files: [droppedFile], types: ['Files'] },
|
||||
})
|
||||
|
||||
expect(screen.queryByText('dataset.newKnowledge.dropFilesHere')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.dragEnter(documentSurface!, {
|
||||
dataTransfer: { files: [droppedFile], types: ['Files'] },
|
||||
})
|
||||
fireEvent.drop(documentSurface!, {
|
||||
dataTransfer: {
|
||||
dropEffect: 'copy',
|
||||
@ -1794,9 +1809,38 @@ describe('DocumentsPage', () => {
|
||||
expect(
|
||||
screen.getByRole('heading', { name: 'dataset.newKnowledge.addDocument' }),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.queryByText('dataset.newKnowledge.dropFilesHere')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('handbook.md')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps the designed drop target active over the open upload form', async () => {
|
||||
const droppedFile = new File(['# handbook'], 'handbook.md', { type: 'text/markdown' })
|
||||
|
||||
render(<DocumentsPage knowledgeSpaceId="space-1" />, { searchParams: '?upload=1' })
|
||||
|
||||
const documentSurface = screen
|
||||
.getByRole('heading', { name: 'dataset.newKnowledge.addDocument' })
|
||||
.closest('section')
|
||||
expect(documentSurface).not.toBeNull()
|
||||
|
||||
fireEvent.dragEnter(documentSurface!, {
|
||||
dataTransfer: { files: [droppedFile], types: ['Files'] },
|
||||
})
|
||||
|
||||
expect(screen.getByText('dataset.newKnowledge.dropFilesHere')).toBeInTheDocument()
|
||||
|
||||
fireEvent.drop(documentSurface!, {
|
||||
dataTransfer: {
|
||||
dropEffect: 'copy',
|
||||
files: [droppedFile],
|
||||
types: ['Files'],
|
||||
},
|
||||
})
|
||||
|
||||
expect(screen.queryByText('dataset.newKnowledge.dropFilesHere')).not.toBeInTheDocument()
|
||||
expect(await screen.findByText('handbook.md')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps direct-upload actions unavailable until the deployment is verified', () => {
|
||||
systemFeaturesStateMock.uploadEnabled = false
|
||||
|
||||
|
||||
@ -921,7 +921,7 @@ export function DocumentDropOverlay() {
|
||||
className="pointer-events-none absolute inset-0 z-30 flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-divider-regular bg-[rgba(255,255,255,0.5)] text-center backdrop-blur-[5px]"
|
||||
role="status"
|
||||
>
|
||||
<div className="flex w-57 items-center justify-center gap-5 rounded-xl border border-dashed border-divider-regular bg-components-panel-bg px-8 py-7 shadow-xs">
|
||||
<div className="flex h-22 w-57 items-center justify-center gap-2 rounded-xl border border-dashed border-divider-regular bg-components-panel-bg shadow-xs">
|
||||
<span aria-hidden className="i-ri-file-word-2-fill size-6 text-text-accent" />
|
||||
<span aria-hidden className="i-ri-file-pdf-2-fill size-6 text-text-destructive" />
|
||||
<span aria-hidden className="i-ri-file-excel-fill size-6 text-text-success" />
|
||||
@ -930,7 +930,7 @@ export function DocumentDropOverlay() {
|
||||
<p className="mt-4 system-md-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.dropFilesHere'])}
|
||||
</p>
|
||||
<p className="mt-2 system-xs-regular text-text-tertiary">
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.documentUploadFormats'])}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import type { Ref } from 'react'
|
||||
import type { KnowledgeFsUploadPhase } from './knowledge-fs-upload'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DocumentUploadFileList } from './document-upload-file-list'
|
||||
import {
|
||||
@ -12,6 +13,21 @@ import {
|
||||
uniqueDocumentUploadFiles,
|
||||
} from './document-upload-policy'
|
||||
|
||||
export type DocumentUploadFormHandle = {
|
||||
addFiles: (files: File[]) => void
|
||||
}
|
||||
|
||||
type DocumentUploadFormProps = {
|
||||
initialFiles?: File[]
|
||||
onCancel: () => void
|
||||
onFilesAdded: (files: File[]) => Promise<void>
|
||||
onFileRemoved: (file: File) => void
|
||||
onSubmit: (files: File[]) => Promise<boolean>
|
||||
uploadProgress?: ReadonlyMap<File, KnowledgeFsUploadPhase>
|
||||
uploading: boolean
|
||||
ref?: Ref<DocumentUploadFormHandle>
|
||||
}
|
||||
|
||||
export function DocumentUploadForm({
|
||||
initialFiles = [],
|
||||
onCancel,
|
||||
@ -20,15 +36,8 @@ export function DocumentUploadForm({
|
||||
onSubmit,
|
||||
uploadProgress = new Map(),
|
||||
uploading,
|
||||
}: {
|
||||
initialFiles?: File[]
|
||||
onCancel: () => void
|
||||
onFilesAdded: (files: File[]) => Promise<void>
|
||||
onFileRemoved: (file: File) => void
|
||||
onSubmit: (files: File[]) => Promise<boolean>
|
||||
uploadProgress?: ReadonlyMap<File, KnowledgeFsUploadPhase>
|
||||
uploading: boolean
|
||||
}) {
|
||||
ref,
|
||||
}: DocumentUploadFormProps) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
@ -62,6 +71,19 @@ export function DocumentUploadForm({
|
||||
[onFilesAdded],
|
||||
)
|
||||
|
||||
const addFiles = useCallback(
|
||||
(nextFiles: File[]) => {
|
||||
const uniqueFiles = uniqueDocumentUploadFiles(filesRef.current, nextFiles)
|
||||
const validUniqueFiles = uniqueFiles.filter((file) => !documentUploadIssue(file))
|
||||
filesRef.current = [...filesRef.current, ...uniqueFiles]
|
||||
setFiles(filesRef.current)
|
||||
stageAddedFiles(validUniqueFiles)
|
||||
},
|
||||
[stageAddedFiles],
|
||||
)
|
||||
|
||||
useImperativeHandle(ref, () => ({ addFiles }), [addFiles])
|
||||
|
||||
useEffect(() => {
|
||||
if (initialFilesAnnouncedRef.current) return
|
||||
initialFilesAnnouncedRef.current = true
|
||||
@ -78,14 +100,6 @@ export function DocumentUploadForm({
|
||||
})
|
||||
}, [initialFiles, onFilesAdded])
|
||||
|
||||
const addFiles = (nextFiles: File[]) => {
|
||||
const uniqueFiles = uniqueDocumentUploadFiles(filesRef.current, nextFiles)
|
||||
const validUniqueFiles = uniqueFiles.filter((file) => !documentUploadIssue(file))
|
||||
filesRef.current = [...filesRef.current, ...uniqueFiles]
|
||||
setFiles(filesRef.current)
|
||||
stageAddedFiles(validUniqueFiles)
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
aria-labelledby="new-knowledge-documents-title"
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import type { DocumentAction } from './document-actions-dropdown'
|
||||
import type { DocumentProcessingTask } from './document-models'
|
||||
import type { DocumentUploadFormHandle } from './document-upload-form'
|
||||
import type { DocumentUploadIssue } from './document-upload-policy'
|
||||
import type { KnowledgeFsUploadPhase, KnowledgeFsUploadProgress } from './knowledge-fs-upload'
|
||||
import type {
|
||||
@ -299,6 +300,7 @@ export function DocumentsPage({ knowledgeSpaceId }: { knowledgeSpaceId: string }
|
||||
)
|
||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<Set<string>>(() => new Set())
|
||||
const [uploadFormInitialFiles, setUploadFormInitialFiles] = useState<File[]>([])
|
||||
const uploadFormRef = useRef<DocumentUploadFormHandle>(null)
|
||||
const [isFileDragActive, setIsFileDragActive] = useState(false)
|
||||
const fileDragDepthRef = useRef(0)
|
||||
const [tasksOpen, setTasksOpen] = useState(false)
|
||||
@ -2594,7 +2596,7 @@ export function DocumentsPage({ knowledgeSpaceId }: { knowledgeSpaceId: string }
|
||||
const types = Array.from(event.dataTransfer.types ?? [])
|
||||
if (types.length && !types.includes('Files')) return
|
||||
event.preventDefault()
|
||||
if (!canUpload || uploadFormOpen || uploading) return
|
||||
if (!canUpload || uploading) return
|
||||
fileDragDepthRef.current += 1
|
||||
setIsFileDragActive(true)
|
||||
}}
|
||||
@ -2607,8 +2609,7 @@ export function DocumentsPage({ knowledgeSpaceId }: { knowledgeSpaceId: string }
|
||||
const types = Array.from(event.dataTransfer.types ?? [])
|
||||
if (types.length && !types.includes('Files')) return
|
||||
event.preventDefault()
|
||||
if (!uploadFormOpen)
|
||||
event.dataTransfer.dropEffect = canUpload && !uploading ? 'copy' : 'none'
|
||||
event.dataTransfer.dropEffect = canUpload && !uploading ? 'copy' : 'none'
|
||||
}}
|
||||
onDrop={(event) => {
|
||||
const types = Array.from(event.dataTransfer.types ?? [])
|
||||
@ -2616,9 +2617,11 @@ export function DocumentsPage({ knowledgeSpaceId }: { knowledgeSpaceId: string }
|
||||
event.preventDefault()
|
||||
fileDragDepthRef.current = 0
|
||||
setIsFileDragActive(false)
|
||||
if (!canUpload || uploadFormOpen || uploading) return
|
||||
if (!canUpload || uploading) return
|
||||
const files = [...event.dataTransfer.files]
|
||||
if (files.length) openUploadForm(files)
|
||||
if (!files.length) return
|
||||
if (uploadFormOpen) uploadFormRef.current?.addFiles(files)
|
||||
else openUploadForm(files)
|
||||
}}
|
||||
onBlurCapture={(event) => {
|
||||
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
|
||||
@ -2860,6 +2863,7 @@ export function DocumentsPage({ knowledgeSpaceId }: { knowledgeSpaceId: string }
|
||||
</div>
|
||||
) : uploadFormOpen ? (
|
||||
<DocumentUploadForm
|
||||
ref={uploadFormRef}
|
||||
initialFiles={uploadFormInitialFiles}
|
||||
uploadProgress={stagedUploadProgress}
|
||||
uploading={uploading}
|
||||
@ -2952,7 +2956,7 @@ export function DocumentsPage({ knowledgeSpaceId }: { knowledgeSpaceId: string }
|
||||
uploading={uploading}
|
||||
/>
|
||||
)}
|
||||
{isFileDragActive && canUpload && !uploadFormOpen && <DocumentDropOverlay />}
|
||||
{isFileDragActive && canUpload && <DocumentDropOverlay />}
|
||||
</section>
|
||||
{bulkActionsVisible && (
|
||||
<DocumentBulkActions
|
||||
|
||||
Loading…
Reference in New Issue
Block a user