mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
c1c098e2f9
GitOrigin-RevId: f926666c032d1398a0e3f72a298116a3c7a9cd75
149 lines
4.7 KiB
TypeScript
149 lines
4.7 KiB
TypeScript
import React, { useEffect, useState } from 'react'
|
|
import withErrorBoundary from '../../../infrastructure/error-boundary'
|
|
import { useProjectContext } from '../../../shared/context/project-context'
|
|
import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
|
|
import FileTreeContext from './file-tree-context'
|
|
import FileTreeDraggablePreviewLayer from './file-tree-draggable-preview-layer'
|
|
import FileTreeFolderList from './file-tree-folder-list'
|
|
import FileTreeToolbar from './file-tree-toolbar'
|
|
import FileTreeModalDelete from './modals/file-tree-modal-delete'
|
|
import FileTreeModalCreateFolder from './modals/file-tree-modal-create-folder'
|
|
import FileTreeModalError from './modals/file-tree-modal-error'
|
|
import FileTreeContextMenu from './file-tree-context-menu'
|
|
import FileTreeError from './file-tree-error'
|
|
import { useDroppable } from '../contexts/file-tree-draggable'
|
|
import { useFileTreeSocketListener } from '../hooks/file-tree-socket-listener'
|
|
import FileTreeModalCreateFile from './modals/file-tree-modal-create-file'
|
|
import FileTreeInner from './file-tree-inner'
|
|
import { useDragLayer } from 'react-dnd'
|
|
import classnames from 'classnames'
|
|
import { pathInFolder } from '@/features/file-tree/util/path'
|
|
|
|
const FileTreeRoot = React.memo<{
|
|
onSelect: () => void
|
|
onDelete: () => void
|
|
onInit: () => void
|
|
isConnected: boolean
|
|
setRefProviderEnabled: () => void
|
|
setStartedFreeTrial: () => void
|
|
reindexReferences: () => void
|
|
refProviders: Record<string, boolean>
|
|
}>(function FileTreeRoot({
|
|
refProviders,
|
|
reindexReferences,
|
|
setRefProviderEnabled,
|
|
setStartedFreeTrial,
|
|
onSelect,
|
|
onInit,
|
|
onDelete,
|
|
isConnected,
|
|
}) {
|
|
const [fileTreeContainer, setFileTreeContainer] =
|
|
useState<HTMLDivElement | null>(null)
|
|
const { _id: projectId } = useProjectContext()
|
|
const { fileTreeData } = useFileTreeData()
|
|
const isReady = Boolean(projectId && fileTreeData)
|
|
|
|
useEffect(() => {
|
|
if (fileTreeContainer) {
|
|
const listener = (event: DragEvent) => {
|
|
if (event.dataTransfer) {
|
|
// store the dragged entity in dataTransfer
|
|
const { dataset } = event.target as HTMLDivElement
|
|
if (
|
|
dataset.fileId &&
|
|
dataset.fileType &&
|
|
dataset.fileType !== 'folder'
|
|
) {
|
|
event.dataTransfer.setData(
|
|
'application/x-overleaf-file-id',
|
|
dataset.fileId
|
|
)
|
|
|
|
const filePath = pathInFolder(fileTreeData, dataset.fileId)
|
|
if (filePath) {
|
|
event.dataTransfer.setData(
|
|
'application/x-overleaf-file-path',
|
|
filePath
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileTreeContainer.addEventListener('dragstart', listener)
|
|
|
|
return () => {
|
|
fileTreeContainer.removeEventListener('dragstart', listener)
|
|
}
|
|
}
|
|
}, [fileTreeContainer, fileTreeData])
|
|
|
|
useEffect(() => {
|
|
if (isReady) onInit()
|
|
}, [isReady, onInit])
|
|
if (!isReady) return null
|
|
|
|
return (
|
|
<div
|
|
className="file-tree"
|
|
data-testid="file-tree"
|
|
ref={setFileTreeContainer}
|
|
>
|
|
{fileTreeContainer && (
|
|
<FileTreeContext
|
|
refProviders={refProviders}
|
|
setRefProviderEnabled={setRefProviderEnabled}
|
|
setStartedFreeTrial={setStartedFreeTrial}
|
|
reindexReferences={reindexReferences}
|
|
onSelect={onSelect}
|
|
fileTreeContainer={fileTreeContainer}
|
|
>
|
|
{isConnected ? null : <div className="disconnected-overlay" />}
|
|
<FileTreeToolbar />
|
|
<FileTreeContextMenu />
|
|
<FileTreeInner>
|
|
<FileTreeRootFolder onDelete={onDelete} />
|
|
</FileTreeInner>
|
|
<FileTreeModalDelete />
|
|
<FileTreeModalCreateFile />
|
|
<FileTreeModalCreateFolder />
|
|
<FileTreeModalError />
|
|
</FileTreeContext>
|
|
)}
|
|
</div>
|
|
)
|
|
})
|
|
|
|
function FileTreeRootFolder({ onDelete }: { onDelete: () => void }) {
|
|
useFileTreeSocketListener(onDelete)
|
|
const { fileTreeData } = useFileTreeData()
|
|
|
|
const { isOver, dropRef } = useDroppable(fileTreeData._id)
|
|
|
|
const dragLayer = useDragLayer(monitor => ({
|
|
isDragging: monitor.isDragging(),
|
|
item: monitor.getItem(),
|
|
clientOffset: monitor.getClientOffset(),
|
|
}))
|
|
|
|
return (
|
|
<>
|
|
<FileTreeDraggablePreviewLayer isOver={isOver} {...dragLayer} />
|
|
<FileTreeFolderList
|
|
folders={fileTreeData.folders}
|
|
docs={fileTreeData.docs}
|
|
files={fileTreeData.fileRefs}
|
|
classes={{
|
|
root: classnames('file-tree-list', {
|
|
'file-tree-dragging': dragLayer.isDragging,
|
|
}),
|
|
}}
|
|
dropRef={dropRef as any}
|
|
dataTestId="file-tree-list-root"
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default withErrorBoundary(FileTreeRoot, FileTreeError)
|