Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Folder Explorer as an independent component #2529

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions react/src/components/BAIModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ export const DEFAULT_BAI_MODAL_Z_INDEX = 1001;
export interface BAIModalProps extends ModalProps {
okText?: string; // customize text of ok button with adequate content
draggable?: boolean; // modal can be draggle
className?: string;
}
const BAIModal: React.FC<BAIModalProps> = ({ styles, ...modalProps }) => {
const BAIModal: React.FC<BAIModalProps> = ({
className,
styles,
...modalProps
}) => {
const { token } = theme.useToken();
const [disabled, setDisabled] = useState(true);
const [bounds, setBounds] = useState({
Expand Down Expand Up @@ -46,7 +51,7 @@ const BAIModal: React.FC<BAIModalProps> = ({ styles, ...modalProps }) => {
keyboard={false}
{...modalProps}
centered={modalProps.centered ?? true}
className="bai-modal"
className={`bai-modal ${className ?? ''}`}
ironAiken2 marked this conversation as resolved.
Show resolved Hide resolved
wrapClassName={modalProps.draggable ? 'draggable' : ''}
styles={{
...styles,
Expand Down
26 changes: 15 additions & 11 deletions react/src/components/FolderExplorerOpener.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { useBaiSignedRequestWithPromise } from '../helper';
import { useCurrentProjectValue } from '../hooks/useCurrentProject';
import { jotaiStore } from './DefaultProviders';
import LegacyFolderExplorer from './LegacyFolderExplorer';
import { VFolder } from './VFolderSelect';
import { atom, useAtomValue } from 'jotai';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { StringParam, useQueryParam } from 'use-query-params';

// TODO: Separate Folder Explorer from `backend-ai-data-view` and make it opened directly on all pages.

const isDataViewReadyAtom = atom(false);
document.addEventListener('backend-ai-data-view:connected', () => {
jotaiStore.set(isDataViewReadyAtom, true);
Expand All @@ -18,6 +17,8 @@ document.addEventListener('backend-ai-data-view:disconnected', () => {

const FolderExplorerOpener = () => {
const [folderId] = useQueryParam('folder', StringParam) || '';
const [vfolderName, setVFolderName] = useState<string>('');
const [open, setOpen] = useState(false);
const normalizedFolderId = folderId?.replaceAll('-', '');
const currentProject = useCurrentProjectValue();
const baiRequestWithPromise = useBaiSignedRequestWithPromise();
Expand All @@ -38,22 +39,25 @@ const FolderExplorerOpener = () => {
// `id` of `/folders` API is not UUID, but UUID without `-`
(vFolder) => vFolder.id === normalizedFolderId,
);
document.dispatchEvent(
new CustomEvent('folderExplorer:open', {
detail: {
vFolder,
},
}),
);
setVFolderName(vFolder?.name || '');
})
.catch(() => {
// do nothing
});
setOpen(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDataViewReady, folderId]); // don't need to watch `folderId` because this used only once right after the backend-ai-data-view is ready

return null;
return (
<LegacyFolderExplorer
vfolderName={vfolderName}
vfolderID={normalizedFolderId || ''}
open={open}
onRequestClose={() => setOpen(false)}
destroyOnClose
/>
);
};

export default FolderExplorerOpener;
206 changes: 206 additions & 0 deletions react/src/components/LegacyFolderExplorer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import BAIModal, { BAIModalProps } from './BAIModal';
import Flex from './Flex';
import {
DeleteOutlined,
FileAddOutlined,
FolderAddOutlined,
UploadOutlined,
} from '@ant-design/icons';
import {
Button,
Dropdown,
Grid,
Image,
Tooltip,
Typography,
theme,
} from 'antd';
import { createStyles } from 'antd-style';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';

const useStyles = createStyles(({ token, css }) => ({
baiModalHeader: css`
.ant-modal-title {
width: 100%;
margin-right: ${token.marginXXL}px;
}
`,
}));

interface LegacyFolderExplorerProps extends BAIModalProps {
vfolderName: string;
vfolderID: string;
onRequestClose: () => void;
}

const LegacyFolderExplorer: React.FC<LegacyFolderExplorerProps> = ({
vfolderName,
vfolderID,
onRequestClose,
...modalProps
}) => {
const { t } = useTranslation();
const { token } = theme.useToken();
const { styles } = useStyles();
const { lg } = Grid.useBreakpoint();
const [isWritable, setIsWritable] = useState<boolean>(false);
const [isSelected, setIsSelected] = useState<boolean>(false);
// TODO: Events are sent and received as normal,
// but the Lit Element is not rendered and the values inside are not available but ref is available.
const folderExplorerRef = useRef<HTMLDivElement>(null);
const navigate = useNavigate();

useEffect(() => {
const handleConnected = (e: any) => {
setIsWritable(e.detail || false);
};

const handleColumnSelected = (e: any) => {
setIsSelected(e.detail || false);
};

document.addEventListener('folderExplorer:connected', handleConnected);
document.addEventListener(
'folderExplorer:columnSelected',
handleColumnSelected,
);
return () => {
document.removeEventListener('folderExplorer:connected', handleConnected);
document.removeEventListener(
'folderExplorer:columnSelected',
handleColumnSelected,
);
};
}, []);

return (
<BAIModal
className={styles.baiModalHeader}
centered
width={1200}
destroyOnClose
footer={null}
title={
<Flex justify="between" gap={token.marginMD} style={{ width: '100%' }}>
<Flex gap={token.marginMD} style={{ flex: 1 }}>
<Tooltip title={vfolderName}>
<Typography.Title
level={3}
style={{ marginTop: token.marginSM }}
ellipsis
>
{vfolderName}
</Typography.Title>
</Tooltip>
</Flex>
<Flex justify="end" gap={token.marginSM} style={{ flex: lg ? 2 : 1 }}>
<Button
danger
disabled={!isSelected || !isWritable}
icon={<DeleteOutlined />}
onClick={() => {
// @ts-ignore
folderExplorerRef.current?._openDeleteMultipleFileDialog();
}}
>
{lg && t('button.Delete')}
</Button>
<Button
disabled={!isWritable}
icon={<FolderAddOutlined />}
onClick={() => {
//@ts-ignore
folderExplorerRef.current?.openMkdirDialog();
}}
>
{lg && t('button.Create')}
</Button>
<Dropdown
disabled={!isWritable}
menu={{
items: [
{
key: 'upload files',
label: t('data.explorer.UploadFiles'),
icon: <FileAddOutlined />,
onClick: () => {
// @ts-ignore
folderExplorerRef.current?.handleUpload('file');
},
},
{
key: 'upload folder',
label: t('data.explorer.UploadFolder'),
icon: <FolderAddOutlined />,
onClick: () => {
// @ts-ignore
folderExplorerRef.current?.handleUpload('folder');
},
},
],
}}
>
<Button icon={<UploadOutlined />}>{lg && 'Upload'}</Button>
</Dropdown>
<Button
icon={
<Image
width="18px"
src="/resources/icons/filebrowser.svg"
alt="File Browser"
preview={false}
/>
}
onClick={() =>
// @ts-ignore
folderExplorerRef.current?._executeFileBrowser()
}
>
{lg && t('data.explorer.ExecuteFileBrowser')}
</Button>
<Button
icon={
<Image
width="18px"
src="/resources/icons/sftp.png"
alt="SSH / SFTP"
preview={false}
/>
}
onClick={() => {
// @ts-ignore
folderExplorerRef.current?._executeSSHProxyAgent();
}}
>
{lg && t('data.explorer.RunSSH/SFTPserver')}
</Button>
</Flex>
</Flex>
}
onCancel={() => {
onRequestClose();
const queryParams = new URLSearchParams(window.location.search).get(
'tab',
);
if (queryParams) {
navigate(`?tab=${queryParams}`);
} else {
navigate('/data');
}
}}
{...modalProps}
>
{/* @ts-ignore */}
<backend-ai-folder-explorer
ref={folderExplorerRef}
active
vfolderID={vfolderID}
style={{ width: '100%' }}
/>
</BAIModal>
);
};

export default LegacyFolderExplorer;
7 changes: 4 additions & 3 deletions resources/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,6 @@
"Delete": "Löschen",
"FolderOptionUpdate": "Ordneroption aktualisieren",
"Leave": "Verlassen",
"RenameAFolder": "Einen Ordner umbenennen",
"TypeNewFolderName": "Geben Sie einen neuen Ordnernamen ein",
"FolderCreated": "Ordner erstellt",
"FolderCloned": "Ordner geklont",
Expand Down Expand Up @@ -711,7 +710,6 @@
"FileExtensionChanged": "Möchten Sie die Dateierweiterung ändern?",
"KeepFileExtension": "Behalten",
"UseNewFileExtension": "Benutzen",
"RemoveFileExtension": "Dateierweiterung entfernen",
"ExecutingFileBrowser": "Dateibrowser wird ausgeführt...",
"ExecuteFileBrowser": "Dateibrowser ausführen",
"NotEnoughResourceForFileBrowserSession": "Nicht genügend Ressourcen (CPU: 1 Core, Speicher: 0,5 GB), um die Sitzung für den Dateibrowser zu erstellen. Bitte überprüfen Sie die verfügbaren Ressourcen.",
Expand All @@ -727,7 +725,10 @@
"StartingSSH/SFTPSession": "SFTP-Sitzung starten...",
"NumberOfSFTPSessionsExceededTitle": "Limit der laufenden Upload-Session erreicht",
"NumberOfSFTPSessionsExceededBody": "Sie führen alle verfügbaren Upload-Sitzungen aus, die Sie erstellen können. Bitte beenden Sie ungenutzte Upload-Sitzungen, bevor Sie eine neue Sitzung starten.",
"DownloadNotAllowed": "Das Herunterladen von Dateien/Ordnern ist in diesem Ordner nicht gestattet."
"DownloadNotAllowed": "Das Herunterladen von Dateien/Ordnern ist in diesem Ordner nicht gestattet.",
"RenameAFolder": "Ordner umbenennen",
"Filename": "Dateiname",
"RemoveFileExtension": "Entfernen "
},
"invitation": {
"NoValidEmails": "Es wurden keine gültigen E-Mails eingegeben",
Expand Down
7 changes: 4 additions & 3 deletions resources/i18n/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,6 @@
"Delete": "Διαγράφω",
"FolderOptionUpdate": "Ενημέρωση επιλογής φακέλου",
"Leave": "Αδεια",
"RenameAFolder": "Μετονομάστε ένα φάκελο",
"TypeNewFolderName": "Πληκτρολογήστε νέο όνομα φακέλου",
"FolderCreated": "Δημιουργήθηκε φάκελος",
"FolderCloned": "Ο φάκελος κλωνοποιήθηκε",
Expand Down Expand Up @@ -711,7 +710,6 @@
"FileExtensionChanged": "Θέλετε να αλλάξετε την επέκταση αρχείου;",
"KeepFileExtension": "Διατήρηση",
"UseNewFileExtension": "Χρήση",
"RemoveFileExtension": "αφαιρέστε την επέκταση αρχείου",
"ExecutingFileBrowser": "Εκτέλεση προγράμματος περιήγησης ...",
"ExecuteFileBrowser": "Εκτελέστε πρόγραμμα περιήγησης αρχείων",
"NotEnoughResourceForFileBrowserSession": "Δεν υπάρχουν αρκετοί πόροι (cpu: 1 Core, mem: 0,5 GB) για τη δημιουργία της περιόδου λειτουργίας για το πρόγραμμα περιήγησης αρχείων παρακαλούμε ελέγξτε τους διαθέσιμους πόρους.",
Expand All @@ -727,7 +725,10 @@
"StartingSSH/SFTPSession": "Έναρξη συνεδρίας SFTP...",
"NumberOfSFTPSessionsExceededTitle": "Έφθασε το όριο του αριθμού των τρεχουσών συνόδων μεταφόρτωσης",
"NumberOfSFTPSessionsExceededBody": "Εκτελείτε όλες τις διαθέσιμες συνεδρίες μεταφόρτωσης που επιτρέπεται να δημιουργήσετε. Παρακαλούμε τερματίστε τις αχρησιμοποίητες συνεδρίες μεταφόρτωσης πριν ξεκινήσετε μια νέα συνεδρία.",
"DownloadNotAllowed": "Η λήψη αρχείου/φακέλου δεν επιτρέπεται σε αυτόν τον φάκελο."
"DownloadNotAllowed": "Η λήψη αρχείου/φακέλου δεν επιτρέπεται σε αυτόν τον φάκελο.",
"RenameAFolder": "Μετονομασία φακέλου",
"Filename": "Ονομα αρχείου",
"RemoveFileExtension": "Αφαιρώ "
},
"invitation": {
"NoValidEmails": "Δεν καταχωρήθηκαν έγκυρα μηνύματα ηλεκτρονικού ταχυδρομείου",
Expand Down
7 changes: 4 additions & 3 deletions resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,6 @@
"Delete": "Delete",
"FolderOptionUpdate": "Update folder option",
"Leave": "Leave",
"RenameAFolder": "Rename a folder",
"TypeNewFolderName": "Type new folder name",
"FolderCreated": "Folder created",
"FolderCloned": "Folder cloned",
Expand Down Expand Up @@ -846,7 +845,6 @@
"FileExtensionChanged": "Would you like to change the file extension?",
"KeepFileExtension": "Keep ",
"UseNewFileExtension": "Use ",
"RemoveFileExtension": "remove file extension",
"ExecutingFileBrowser": "Executing filebrowser...",
"ExecuteFileBrowser": "Execute filebrowser",
"NotEnoughResourceForFileBrowserSession": "No enough resources(cpu: 1 Core, mem: 0.5GB) to create the session for filebrowser. please check the available resources.",
Expand All @@ -860,7 +858,10 @@
"EmptyFilesAndFoldersAreNotUploaded": "Empty files and empty folders are not uploaded",
"NumberOfSFTPSessionsExceededTitle": "Reached limit of running upload session count",
"NumberOfSFTPSessionsExceededBody": "You are running all available upload sessions you are allowed to create. Please terminated unused upload sessions before starting a new session.",
"DownloadNotAllowed": "Downloading file/folder is not allowed in this folder."
"DownloadNotAllowed": "Downloading file/folder is not allowed in this folder.",
"RenameAFolder": "Rename Folder",
"Filename": "File name",
"RemoveFileExtension": "Remove "
},
"invitation": {
"NoValidEmails": "No valid emails were entered",
Expand Down
Loading
Loading