diff --git a/src/assets/images/copy-files.svg b/src/assets/images/copy-files.svg new file mode 100644 index 0000000..d918be8 --- /dev/null +++ b/src/assets/images/copy-files.svg @@ -0,0 +1,3 @@ + diff --git a/src/components/dialogs/copyfiledlg.tsx b/src/components/dialogs/copyfiledlg.tsx new file mode 100644 index 0000000..d795a76 --- /dev/null +++ b/src/components/dialogs/copyfiledlg.tsx @@ -0,0 +1,221 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import ProgressBar from 'react-customizable-progressbar'; +import { FaArrowRight } from 'react-icons/fa'; +import Button from '@/widgets/button'; +import AppMgr from '@/managers/appmgr'; +import { CommandToXRPMgr } from '@/managers/commandstoxrpmgr'; +import { FolderItem } from '@/utils/types'; +import FolderTree from '../folder-tree'; +import DialogFooter from './dialog-footer'; +import { fireGoogleUserTree, getUsernameFromEmail } from '@/utils/google-utils'; + +type CopyFileDlgProps = { + toggleDialog: () => void; +}; + +/** + * CopyFileDlg component for displaying copy file progress. + * @param toggleDialog Function to toggle the visibility of the dialog + * @returns CopyFileDlg component + */ +function CopyFileDlg({ toggleDialog }: CopyFileDlgProps) { + const { t } = useTranslation(); + const [xrpRobotFileList, setXrpRobotFileList] = useState(null); + const [gdriveFileList, setGDriveFileList] = useState(null); + const [isCopying, setIsCopying] = useState(false); + const [progress, setProgress] = useState(0); + const [progressItem, setProgressItem] = useState(''); + const [gdriveSelectedFolder, setGDriveFolder] = useState(null); + const [xrpRobotSelectedFolder, setXrpRobotFolder] = useState(null); + + useEffect(() => { + // Get the XRP Robot's file list + CommandToXRPMgr.getInstance() + .getOnBoardFSTree(false) + .then((fileList) => { + if (fileList) { + const folderTree = JSON.parse(fileList); + setXrpRobotFileList(folderTree); + } + }); + + // Get the google drive's file list + setGDriveFileList(AppMgr.getInstance().getFolderList()); + }, []); + + /* + * Begin copy process + */ + const beginCopy = async () => { + console.log('beginCopy'); + setIsCopying(true); + // begin the copying process + if (xrpRobotSelectedFolder === null || gdriveSelectedFolder === null) { + setIsCopying(false); + return; + } + setIsCopying(true); + const countItems = (items: FolderItem[]): number => { + let count = 0; + for (const item of items) { + count += 1; + if (item.children) { + count += countItems(item.children); + } + } + return count; + }; + const totalItems = countItems(xrpRobotSelectedFolder || []); + let completedItems = 0; + setProgress(0); + + // create a resursive function to process the folder items + const processFolderItems = async (folderItems: FolderItem[], parentFolderId: string) => { + for (const folderItem of folderItems) { + setProgressItem(folderItem.name); + + if (folderItem.children) { + // create the directory in Google Drive + const folder = await AppMgr.getInstance().driveService.createFolder( + folderItem.name, + parentFolderId, + ); + completedItems++; + setProgress(Math.round((completedItems / Math.max(totalItems, 1)) * 100)); + await processFolderItems(folderItem.children || [], folder?.id || ''); + } else { + // for each folder item, we need to read the content from the XRP Robot and save it to Google Drive + const filePath = + folderItem.path === '/' + ? folderItem.path + folderItem.name + : folderItem.path + '/' + folderItem.name; + const content = await CommandToXRPMgr.getInstance().getFileContents(filePath); + const data: string = new TextDecoder().decode(new Uint8Array(content)); + const minetype = + folderItem.name.split('.').pop() === 'py' ? 'text/x-python' : 'text/plain'; + const blob = new Blob([data], { type: minetype }); + await AppMgr.getInstance().driveService.upsertFileToGoogleDrive( + blob, + folderItem.name, + minetype, + undefined, + parentFolderId, + ); + completedItems++; + setProgress(Math.round((completedItems / Math.max(totalItems, 1)) * 100)); + } + } + }; + + await processFolderItems(xrpRobotSelectedFolder, gdriveSelectedFolder[0].id ?? ''); + setIsCopying(false); + // update the Google Drive folder list + fireGoogleUserTree( + getUsernameFromEmail(AppMgr.getInstance().authService.userProfile.email) ?? '', + ); + setTimeout(() => { + toggleDialog(); + }, 1000); + }; + + /** + * Handle selected folder change + */ + const handleXrpRobotFolderSelect = (selectedItem: FolderItem[]) => { + console.log('XRP Robot', selectedItem); + setXrpRobotFolder(selectedItem); + }; + + function handleGDriveFolderSelect(selectedItem: FolderItem[]): void { + console.log('Google Drive', selectedItem); + setGDriveFolder(selectedItem); + } + + return ( +
+
+

+ {t('copyfiles.title')} +

+

+ {t('copyfiles.description')} +

+
+
+ {/* Three-Column Grid Layout */} +
+ {/* XRP Robot Files Column */} +
+
+ + {t('copyfiles.xrprobot')} + +
+
+ +
+
+ {/* Move Icon Column */} +
+
+ +
+
+ Copying: + + {progressItem} + +
+ +
+ {/* Google Drive Column */} +
+
+ + {t('copyfiles.gdrive')} + +
+
+ +
+
+
+ {/* Dialog Footer */} + +
+ ); +} + +export default CopyFileDlg; diff --git a/src/components/dialogs/filesaveasdlg.tsx b/src/components/dialogs/filesaveasdlg.tsx index a03c6ad..3372e97 100644 --- a/src/components/dialogs/filesaveasdlg.tsx +++ b/src/components/dialogs/filesaveasdlg.tsx @@ -45,19 +45,13 @@ function FileSaveAsDialg(fileSaveAsProps: FileSaveAsProps) { AppMgr.getInstance().authService.userProfile.email, ); if (username) { - return folderPath.replace( - `${Constants.GUSERS_FOLDER}${username}/`, - '/XRPCode/', - ); + return folderPath.replace(`${Constants.GUSERS_FOLDER}${username}/`, '/XRPCode/'); } } return folderPath; }; - const findFolderByPath = ( - items: FolderItem[], - targetPath: string, - ): FolderItem | null => { + const findFolderByPath = (items: FolderItem[], targetPath: string): FolderItem | null => { const normalizedTarget = targetPath.endsWith('/') ? targetPath : `${targetPath}/`; for (const item of items) { if (item.children === null) { @@ -115,7 +109,7 @@ function FileSaveAsDialg(fileSaveAsProps: FileSaveAsProps) { gparentId: gFolderId, parentId: '', filetype, - } + }; fileSaveAsProps.saveCallback(fileData); }; @@ -123,16 +117,16 @@ function FileSaveAsDialg(fileSaveAsProps: FileSaveAsProps) { * handleFolderSelection - callback function to handle the selected folder * @param selectedItem */ - const handleFolderSelection = (selectedItem: FolderItem) => { - const path = selectedItem.path === '/' ? `` : selectedItem.path; + const handleFolderSelection = (selectedItem: FolderItem[]) => { + const path = selectedItem[0].path === '/' ? `` : selectedItem[0].path; setSelectedFolder(path); - setGFolderId(selectedItem.id); + setGFolderId(selectedItem[0].id); validateFilename(filename, path); }; /** * handleFilenameInput - * @param e + * @param e */ const handleFilenameInput = (e: React.ChangeEvent) => { const inputName = e.target.value; @@ -148,14 +142,13 @@ function FileSaveAsDialg(fileSaveAsProps: FileSaveAsProps) { const session = EditorMgr.getInstance().getEditorSession(tabId); if (session) { const parentEnd = session.path.lastIndexOf('/'); - const parentFolder = - parentEnd > 0 ? session.path.substring(0, parentEnd + 1) : '/'; + const parentFolder = parentEnd > 0 ? session.path.substring(0, parentEnd + 1) : '/'; const treeFolderPath = toTreeFolderPath(parentFolder); const folderItem = findFolderByPath(folderList, treeFolderPath); const folderPath = folderItem?.path === '/' ? '' - : folderItem?.path ?? (treeFolderPath === '/' ? '' : treeFolderPath); + : (folderItem?.path ?? (treeFolderPath === '/' ? '' : treeFolderPath)); setSelectedFolder(folderPath); setGFolderId(folderItem?.id ?? ''); setFilename(session.name); @@ -169,31 +162,38 @@ function FileSaveAsDialg(fileSaveAsProps: FileSaveAsProps) { */ const handleFocus = (e: React.FocusEvent) => { const dotIndex = e.target.value.lastIndexOf('.'); - const filenameWithoutExtension = dotIndex === -1 ? e.target.value : e.target.value.substring(0, dotIndex); + const filenameWithoutExtension = + dotIndex === -1 ? e.target.value : e.target.value.substring(0, dotIndex); e.target.setSelectionRange(0, filenameWithoutExtension.length); - }; + }; return (
-

{t('saveFileAs')}

-

{t('choose-dest-file')}

+

+ {t('saveFileAs')} +

+

+ {t('choose-dest-file')} +


-
+
- +
{isFileExists && ( - {t('fileExists')} + + {t('fileExists')} + )}

void; @@ -32,15 +32,15 @@ function NewFileDlg(newFileProps: NewFileProps) { const fileOptions: ListItem[] = [ { label: t('blocklyfile'), - image: blockIcon + image: blockIcon, }, { label: t('pythonfile'), - image: pythonicon + image: pythonicon, }, { label: t('other'), - image: fileIcon + image: fileIcon, }, ]; @@ -107,8 +107,8 @@ function NewFileDlg(newFileProps: NewFileProps) { /** * handleFilenameInput - handles the filename input from user - * @param e - * @returns + * @param e + * @returns */ const handleFilenameInput = (e: React.ChangeEvent) => { setFilename(e.target.value); @@ -116,7 +116,7 @@ function NewFileDlg(newFileProps: NewFileProps) { const isValid = Constants.REGEX_FILENAME.test(filename); const parts = selectedFolder.split('/').filter((part) => part !== ''); const foldername = parts.length > 0 ? parts[parts.length - 1] : selectedFolder; - if (!isValid || AppMgr.getInstance().IsFileExists(foldername,filename)) { + if (!isValid || AppMgr.getInstance().IsFileExists(foldername, filename)) { setIsFileExists(true); setIsOkayToSubmit(false); return; @@ -128,8 +128,8 @@ function NewFileDlg(newFileProps: NewFileProps) { /** * findItemInFolderList - recursive function to find the folder item in the folder list - * @param folderList - * @param folder + * @param folderList + * @param folder * @returns folder item or null */ const findItemInFolderList = (folderList: FolderItem[], folder: string): FolderItem | null => { @@ -145,7 +145,7 @@ function NewFileDlg(newFileProps: NewFileProps) { } } return null; - } + }; /** * handleSubmit handler. Gather all data from the form and send back to parent component */ @@ -173,8 +173,8 @@ function NewFileDlg(newFileProps: NewFileProps) { * handleFolderSelection - callback function to handle the selected folder * @param selectedItem */ - const handleFolderSelection = (selectedItem: FolderItem) => { - setSelectedFolder(selectedItem.path); + const handleFolderSelection = (selectedItem: FolderItem[]) => { + setSelectedFolder(selectedItem[0].path); }; /** @@ -212,67 +212,92 @@ function NewFileDlg(newFileProps: NewFileProps) { }; return ( -
+
-

{t('newFile')}

-

{t('chooseNewFile')}

+

+ {t('newFile')} +

+

+ {t('chooseNewFile')} +


-
e.preventDefault()}> - {t('destFolder')} -
+ e.preventDefault()} + > + + {t('destFolder')} + +
-