import type { IWebdavNode, IWebdavAccountIntegration } from '@rocket.chat/core-typings'; import type { SelectOption } from '@rocket.chat/fuselage'; import { Modal, Box, IconButton, Select, ModalHeader, ModalTitle, ModalClose, ModalContent, ModalFooter } from '@rocket.chat/fuselage'; import { useStableCallback, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import { useSort } from '@rocket.chat/ui-client'; import { useMethod, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; import type { MouseEvent } from 'react'; import { useState, useEffect, useCallback } from 'react'; import FilePickerBreadcrumbs from './FilePickerBreadcrumbs'; import WebdavFilePickerGrid from './WebdavFilePickerGrid'; import WebdavFilePickerTable from './WebdavFilePickerTable'; import { sortWebdavNodes } from './lib/sortWebdavNodes'; import FilterByText from '../../../../components/FilterByText'; export type WebdavSortOptions = 'name' | 'size' | 'dataModified'; export type WebdavFilePickerModalProps = { onUpload: (file: File) => Promise; onClose: () => void; account: IWebdavAccountIntegration; }; const WebdavFilePickerModal = ({ onUpload, onClose, account }: WebdavFilePickerModalProps) => { const t = useTranslation(); const getWebdavFilePreview = useMethod('getWebdavFilePreview'); const getWebdavFileList = useMethod('getWebdavFileList'); const getFileFromWebdav = useMethod('getFileFromWebdav'); const dispatchToastMessage = useToastMessageDispatch(); const [typeView, setTypeView] = useState<'list' | 'grid'>('list'); const { sortBy, sortDirection, setSort } = useSort('name'); const [currentFolder, setCurrentFolder] = useState('/'); const [parentFolders, setParentFolders] = useState([]); const [webdavNodes, setWebdavNodes] = useState([]); const [filterText, setFilterText] = useState(''); const debouncedFilter = useDebouncedValue('', 500); const [isLoading, setIsLoading] = useState(false); const showFilePreviews = useStableCallback(async (accountId: string, nodes: (IWebdavNode & { preview?: string })[] | undefined) => { if (!Array.isArray(nodes) || !nodes.length) { return; } const promises = nodes .map((node, index) => { if (node.type !== 'file') { return; } return getWebdavFilePreview(accountId, node.filename) .then((res) => { if (!res?.data) { return; } const blob = new Blob([res?.data], { type: 'image/png' }); const imgURL = URL.createObjectURL(blob); nodes[index].preview = imgURL; }) .catch((e) => e); }) .filter(Boolean); return Promise.all(promises) .then(() => nodes) .catch((e) => e); }); const handleFilterNodes = useCallback( (webdavNodes: IWebdavNode[]): void => { const regex = new RegExp(`\\b${debouncedFilter}`, 'i'); const filteredNodes = webdavNodes.filter(({ basename }) => basename.match(regex)); return setWebdavNodes(filteredNodes); }, [debouncedFilter], ); const handleGetWebdavFileList = useCallback(async (): Promise => { setIsLoading(true); let result; try { result = await getWebdavFileList(account._id, currentFolder); handleFilterNodes(result.data); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); onClose(); } finally { setIsLoading(false); const nodesWithPreviews = await showFilePreviews(account._id, result?.data); if (Array.isArray(nodesWithPreviews) && nodesWithPreviews.length) { handleFilterNodes(nodesWithPreviews); } } }, [account._id, currentFolder, dispatchToastMessage, getWebdavFileList, onClose, showFilePreviews, handleFilterNodes]); const handleBreadcrumb = (e: MouseEvent): void => { const { index } = e.currentTarget.dataset; const parentFolders = currentFolder.split('/').filter((s) => s); // determine parent directory to go to let targetFolder = '/'; for (let i = 0; i <= Number(index); i++) { targetFolder += parentFolders[i]; targetFolder += '/'; } setCurrentFolder(targetFolder); }; const handleBack = (): void => { let newCurrentFolder = currentFolder; // determine parent directory to go back let parentFolder = '/'; if (newCurrentFolder && newCurrentFolder !== '/') { if (newCurrentFolder[newCurrentFolder.length - 1] === '/') { newCurrentFolder = newCurrentFolder.slice(0, -1); } parentFolder = newCurrentFolder.substr(0, newCurrentFolder.lastIndexOf('/') + 1); } setCurrentFolder(parentFolder); }; const handleNodeClick = (webdavNode: IWebdavNode): void | Promise => { if (webdavNode.type === 'directory') { return setCurrentFolder(webdavNode.filename); } return handleUpload(webdavNode); }; const handleUpload = async (webdavNode: IWebdavNode): Promise => { setIsLoading(true); const uploadFile = async (file: File): Promise => { try { await onUpload?.(file); } catch (error) { return dispatchToastMessage({ type: 'error', message: error }); } finally { setIsLoading(false); onClose(); } }; try { const { data } = await getFileFromWebdav(account._id, webdavNode); const blob = new Blob([data]); const file = new File([blob], webdavNode.basename, { type: webdavNode.mime }); await uploadFile(file); } catch (error) { return dispatchToastMessage({ type: 'error', message: error }); } }; useEffect(() => { handleGetWebdavFileList(); }, [handleGetWebdavFileList]); useEffect(() => { setParentFolders(currentFolder?.split('/').filter((s) => s) || []); }, [currentFolder]); const options: SelectOption[] = [ ['name', 'Name'], ['size', 'Size'], ['dataModified', 'Data Modified'], ]; const handleSort = (sortBy: WebdavSortOptions, sortDirection?: 'asc' | 'desc'): void => { setSort(sortBy); const sortedNodes = sortWebdavNodes(webdavNodes, sortBy, sortDirection); return setWebdavNodes(sortedNodes); }; return ( {t('Upload_From', { name: account.name })} {typeView === 'list' && setTypeView('grid')} />} {typeView === 'grid' && setTypeView('list')} />} setFilterText(event.target.value)}> {typeView === 'grid' && (