Loading...
diff --git a/frontend/src/api_helper/TreeWrapper.ts b/frontend/src/api_helper/TreeWrapper.ts index d33501fac..206f6c505 100644 --- a/frontend/src/api_helper/TreeWrapper.ts +++ b/frontend/src/api_helper/TreeWrapper.ts @@ -7,6 +7,13 @@ const isSuccessful = (response: AxiosResponse) => { return response.status >= 200 && response.status < 300; }; +const axiosExtra = { + headers: { + //@ts-ignore Needed for compatibility with Unibotics + "X-CSRFToken": context.csrf, + }, +}; + // File management const getFileList = async (projectName: string) => { @@ -28,6 +35,26 @@ const getFileList = async (projectName: string) => { } }; +const getFile = async (projectName: string, fileName: string) => { + if (!projectName) throw new Error("Project name is not set"); + if (!fileName) throw new Error("File name is not set"); + + const apiUrl = `/bt_studio/get_file?project_name=${encodeURIComponent(projectName)}&filename=${encodeURIComponent(fileName)}`; + + try { + const response = await axios.get(apiUrl); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to get file list."); // Response error + } + + return response.data.content; + } catch (error: unknown) { + throw error; // Rethrow + } +}; + const getActionsList = async (projectName: string) => { if (!projectName) throw new Error("Project name is not set"); @@ -47,6 +74,37 @@ const getActionsList = async (projectName: string) => { } }; +const saveFile = async ( + projectName: string, + fileName: string, + content: string +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!fileName) throw new Error("Current File name is not set"); + if (!content) throw new Error("Content does not exist"); + + const apiUrl = "/bt_studio/save_file/"; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + filename: fileName, + content: content, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to create project."); // Response error + } + } catch (error) { + throw error; // Rethrow + } +}; + // Project management const createProject = async (projectName: string) => { @@ -54,10 +112,16 @@ const createProject = async (projectName: string) => { throw new Error("Project name cannot be empty."); } - const apiUrl = `/bt_studio/create_project?project_name=${encodeURIComponent(projectName)}`; + const apiUrl = `/bt_studio/create_project/`; try { - const response = await axios.get(apiUrl); + const response = await axios.post( + apiUrl, + { + project_name: projectName, + }, + axiosExtra + ); // Handle unsuccessful response status (e.g., non-2xx status) if (!isSuccessful(response)) { @@ -68,6 +132,31 @@ const createProject = async (projectName: string) => { } }; +const deleteProject = async (projectName: string) => { + if (!projectName.trim()) { + throw new Error("Project name cannot be empty."); + } + + const apiUrl = `/bt_studio/delete_project/`; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to delete project."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + const saveBaseTree = async (modelJson: string, currentProjectname: string) => { if (!modelJson) throw new Error("Tree JSON is empty!"); if (!currentProjectname) throw new Error("Current Project name is not set"); @@ -80,12 +169,7 @@ const saveBaseTree = async (modelJson: string, currentProjectname: string) => { project_name: currentProjectname, graph_json: JSON.stringify(modelJson), }, - { - headers: { - //@ts-ignore Needed for compatibility with Unibotics - "X-CSRFToken": context.csrf, - }, - } + axiosExtra ); // Handle unsuccessful response status (e.g., non-2xx status) @@ -134,6 +218,33 @@ const loadProjectConfig = async ( } }; +const saveProjectConfig = async ( + currentProjectname: string, + settings: string +) => { + if (!currentProjectname) throw new Error("Current Project name is not set"); + if (!settings) throw new Error("Settings content is null"); + + const apiUrl = "/bt_studio/save_project_configuration/"; + try { + const response = await axios.post( + apiUrl, + { + project_name: currentProjectname, + settings: settings, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to create project."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + const getProjectGraph = async (currentProjectname: string) => { if (!currentProjectname) throw new Error("Current Project name is not set"); @@ -220,12 +331,35 @@ const createRoboticsBackendUniverse = async ( universe_name: universeName, id: universeId, }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to save subtree."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const deleteUniverse = async ( + projectName: string, + universeName: string, +) => { + if (!projectName) throw new Error("The project name is not set"); + if (!universeName) throw new Error("The universe name is not set"); + + const apiUrl = "/bt_studio/delete_universe/"; + + try { + const response = await axios.post( + apiUrl, { - headers: { - //@ts-ignore Needed for compatibility with Unibotics - "X-CSRFToken": context.csrf, - }, - } + project_name: projectName, + universe_name: universeName, + }, + axiosExtra ); // Handle unsuccessful response status (e.g., non-2xx status) @@ -253,13 +387,7 @@ const getCustomUniverseZip = async ( app_name: currentProjectname, universe_name: universeName, }, - { - responseType: "blob", // Ensure the response is treated as a Blob - headers: { - //@ts-ignore Needed for compatibility with Unibotics - "X-CSRFToken": context.csrf, - }, - } + axiosExtra ); // Handle unsuccessful response status (e.g., non-2xx status) @@ -276,33 +404,22 @@ const getCustomUniverseZip = async ( // App management -const generateApp = async ( - modelJson: Object, +const generateLocalApp = async ( currentProjectname: string, btOrder: string ) => { - if (!modelJson) throw new Error("Tree JSON is empty!"); if (!currentProjectname) throw new Error("Current Project name is not set"); if (!btOrder) throw new Error("Behavior Tree order is not set"); - console.log("The modelJson is: ", modelJson); - - const apiUrl = "/bt_studio/generate_app/"; + const apiUrl = `/bt_studio/generate_local_app/`; try { const response = await axios.post( apiUrl, { app_name: currentProjectname, - tree_graph: JSON.stringify(modelJson), bt_order: btOrder, }, - { - responseType: "blob", // Ensure the response is treated as a Blob - headers: { - //@ts-ignore Needed for compatibility with Unibotics - "X-CSRFToken": context.csrf, - }, - } + axiosExtra ); // Handle unsuccessful response status (e.g., non-2xx status) @@ -317,30 +434,21 @@ const generateApp = async ( }; const generateDockerizedApp = async ( - modelJson: Object, currentProjectname: string, btOrder: string ) => { - if (!modelJson) throw new Error("Tree JSON is empty!"); if (!currentProjectname) throw new Error("Current Project name is not set"); if (!btOrder) throw new Error("Behavior Tree order is not set"); - const apiUrl = "/bt_studio/generate_dockerized_app/"; + const apiUrl = `/bt_studio/generate_dockerized_app/`; try { const response = await axios.post( apiUrl, { app_name: currentProjectname, - tree_graph: JSON.stringify(modelJson), bt_order: btOrder, }, - { - responseType: "blob", // Ensure the response is treated as a Blob - headers: { - //@ts-ignore Needed for compatibility with Unibotics - "X-CSRFToken": context.csrf, - }, - } + axiosExtra ); // Handle unsuccessful response status (e.g., non-2xx status) @@ -376,12 +484,7 @@ const createSubtree = async ( project_name: currentProjectname, subtree_name: subtreeName, }, - { - headers: { - //@ts-ignore Needed for compatibility with Unibotics - "X-CSRFToken": context.csrf, - }, - } + axiosExtra ); // Handle unsuccessful response status (e.g., non-2xx status) @@ -451,12 +554,7 @@ const saveSubtree = async ( subtree_name: subtreeName, subtree_json: JSON.stringify(modelJson), }, - { - headers: { - //@ts-ignore Needed for compatibility with Unibotics - "X-CSRFToken": context.csrf, - }, - } + axiosExtra ); // Handle unsuccessful response status (e.g., non-2xx status) @@ -468,22 +566,412 @@ const saveSubtree = async ( } }; +const uploadFile = async ( + projectName: string, + fileName: string, + location: string, + content: string +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!fileName) throw new Error("File name is not set"); + if (!location) throw new Error("Location is not set"); + if (!content) throw new Error("Content is not defined"); + + const apiUrl = "/bt_studio/upload_code/"; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + file_name: fileName, + location: location, + content: content, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to upload file."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const createAction = async ( + projectName: string, + fileName: string, + template: string, +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!fileName) throw new Error("File name is not set"); + if (!template) throw new Error("Template is not set"); + + const apiUrl = "/bt_studio/create_action/"; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + filename: fileName, + template: template, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to upload file."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const createFile = async ( + projectName: string, + fileName: string, + location: string, +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!fileName) throw new Error("File name is not set"); + if (!location) throw new Error("Location is not set"); + + const apiUrl = "/bt_studio/create_file/"; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + location: location, + filename: fileName, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to upload file."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const createFolder = async ( + projectName: string, + folderName: string, + location: string, +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!folderName) throw new Error("Folder name is not set"); + if (!location) throw new Error("Location is not set"); + + const apiUrl = "/bt_studio/create_folder/"; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + location: location, + folder_name: folderName, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to upload file."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const renameFile = async ( + projectName: string, + path: string, + new_path: string, +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!path) throw new Error("Path is not set"); + if (!new_path) throw new Error("New path is not set"); + + const apiUrl = "/bt_studio/rename_file/"; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + path: path, + rename_to: new_path, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to upload file."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const renameFolder = async ( + projectName: string, + path: string, + new_path: string, +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!path) throw new Error("Path is not set"); + if (!new_path) throw new Error("New path is not set"); + + const apiUrl = "/bt_studio/rename_folder/"; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + path: path, + rename_to: new_path, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to upload file."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const deleteFile = async ( + projectName: string, + path: string, +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!path) throw new Error("Path is not set"); + + const apiUrl = "/bt_studio/delete_file/"; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + path: path, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to upload file."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const deleteFolder = async ( + projectName: string, + path: string, +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!path) throw new Error("Path is not set"); + + const apiUrl = "/bt_studio/delete_folder/"; + + try { + const response = await axios.post( + apiUrl, + { + project_name: projectName, + path: path, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to upload file."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const uploadUniverse = async ( + projectName: string, + universeName: string, + uploadedUniverse: any, +) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!universeName) throw new Error("Universe name is not set"); + if (!uploadedUniverse) throw new Error("Content is not set"); + + const apiUrl = "/bt_studio/upload_universe/"; + + try { + const response = await axios.post( + apiUrl, + { + universe_name: universeName, + zip_file: uploadedUniverse, + app_name: projectName, + }, + axiosExtra + ); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to upload file."); // Response error + } + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const listDockerUniverses = async () => { + const apiUrl = `/bt_studio/list_docker_universes`; + + try { + const response = await axios.get(apiUrl); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to get subtree."); // Response error + } + + return response.data.universes; + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const listProjects = async () => { + const apiUrl = `/bt_studio/get_project_list`; + + try { + const response = await axios.get(apiUrl); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to get subtree."); // Response error + } + + return response.data.project_list; + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const listUniverses = async (projectName:string ) => { + if (!projectName) throw new Error("Current Project name is not set"); + + const apiUrl = `/bt_studio/get_universes_list?project_name=${encodeURIComponent(projectName)}`; + + try { + const response = await axios.get(apiUrl); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to get subtree."); // Response error + } + + return response.data.universes_list; + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const getTreeStructure = async (projectName:string, btOrder: string) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!btOrder) throw new Error("Behavior Tree order is not set"); + + const apiUrl = `/bt_studio/get_tree_structure?project_name=${encodeURIComponent(projectName)}&bt_order=${encodeURIComponent(btOrder)}`; + + try { + const response = await axios.get(apiUrl); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to get subtree."); // Response error + } + + return response.data.tree_structure; + } catch (error: unknown) { + throw error; // Rethrow + } +}; + +const getSubtreeStructure = async (projectName:string, subtreeName:string, btOrder: string) => { + if (!projectName) throw new Error("Current Project name is not set"); + if (!subtreeName) throw new Error("Subtree name is not set"); + if (!btOrder) throw new Error("Behavior Tree order is not set"); + + const apiUrl = `/bt_studio/get_subtree_structure?project_name=${encodeURIComponent(projectName)}&subtree_name=${encodeURIComponent(subtreeName)}&bt_order=${encodeURIComponent(btOrder)}`; + + try { + const response = await axios.get(apiUrl); + + // Handle unsuccessful response status (e.g., non-2xx status) + if (!isSuccessful(response)) { + throw new Error(response.data.message || "Failed to get subtree."); // Response error + } + + return response.data.tree_structure; + } catch (error: unknown) { + throw error; // Rethrow + } +}; + + // Named export export { + createAction, + createFile, + createFolder, createProject, - saveBaseTree, - loadProjectConfig, - getProjectGraph, - generateApp, + createRoboticsBackendUniverse, + createSubtree, + deleteFile, + deleteFolder, + deleteProject, + deleteUniverse, generateDockerizedApp, - getUniverseConfig, - getRoboticsBackendUniversePath, + generateLocalApp, + getActionsList, getCustomUniverseZip, - createSubtree, - getSubtreeList, - getSubtree, + getFile, getFileList, - getActionsList, + getProjectGraph, + getRoboticsBackendUniversePath, + getSubtree, + getSubtreeList, + getSubtreeStructure, + getTreeStructure, + getUniverseConfig, + listDockerUniverses, + listProjects, + listUniverses, + loadProjectConfig, + renameFile, + renameFolder, + saveBaseTree, + saveFile, + saveProjectConfig, saveSubtree, - createRoboticsBackendUniverse, + uploadFile, + uploadUniverse, }; diff --git a/frontend/src/components/file_browser/FileBrowser.js b/frontend/src/components/file_browser/FileBrowser.js index 9691333ed..8493066ae 100644 --- a/frontend/src/components/file_browser/FileBrowser.js +++ b/frontend/src/components/file_browser/FileBrowser.js @@ -1,13 +1,25 @@ import React, { useEffect, useState } from "react"; -import axios from "axios"; +import JSZip from "jszip"; import "./FileBrowser.css"; import NewFileModal from "./modals/NewFileModal.jsx"; import RenameModal from "./modals/RenameModal.jsx"; -import NewFolderModal from "./modals/NewFolderModal.jsx"; +import NewFolderModal from "./modals/NewFolderModal"; import UploadModal from "./modals/UploadModal.tsx"; import DeleteModal from "./modals/DeleteModal.jsx"; import FileExplorer from "./file_explorer/FileExplorer.jsx"; +import { + getFile, + getFileList, + createAction, + createFile, + createFolder, + renameFile, + renameFolder, + deleteFile, + deleteFolder, +} from "./../../api_helper/TreeWrapper"; + import { ReactComponent as AddIcon } from "./img/add.svg"; import { ReactComponent as AddFolderIcon } from "./img/add_folder.svg"; import { ReactComponent as DeleteIcon } from "./img/delete.svg"; @@ -20,7 +32,7 @@ function getParentDir(file) { return file.path; } - var split_path = file.path.split("/"); // TODO: add for windows + var split_path = file.path.split("/"); return split_path.slice(0, split_path.length - 1).join("/"); } @@ -32,6 +44,11 @@ const FileBrowser = ({ actionNodesData, showAccentColor, diagramEditorReady, + setAutosave, + forceSaveCurrent, + setForcedSaveCurrent, + forceUpdate, + setSaveCurrentDiagram, }) => { const [fileList, setFileList] = useState(null); const [isNewFileModalOpen, setNewFileModalOpen] = useState(false); @@ -41,6 +58,7 @@ const FileBrowser = ({ const [isUploadModalOpen, setUploadModalOpen] = useState(false); const [selectedEntry, setSelectedEntry] = useState(null); const [deleteEntry, setDeleteEntry] = useState(null); + const [deleteType, setDeleteType] = useState(false); const [renameEntry, setRenameEntry] = useState(null); const [selectedLocation, setSelectedLocation] = useState(""); @@ -48,6 +66,13 @@ const FileBrowser = ({ updateSelectedLocation(null); }, [selectedEntry]); + useEffect(() => { + if (forceUpdate.value) { + forceUpdate.callback(false); + fetchFileList(); + } + }, [forceUpdate.value]); + const updateSelectedLocation = (file) => { if (file) { setSelectedLocation(getParentDir(file)); @@ -64,15 +89,9 @@ const FileBrowser = ({ console.log("Fecthing file list, the project name is:", currentProjectname); if (currentProjectname !== "") { try { - const response = await axios.get( - `/bt_studio/get_file_list?project_name=${currentProjectname}`, - ); - const files = JSON.parse(response.data.file_list); + const file_list = await getFileList(currentProjectname); + const files = JSON.parse(file_list); setFileList(files); - // if (Array.isArray(files)) { - // } else { - // console.error("API response is not an array:", files); - // } } catch (error) { console.error("Error fetching files:", error); } @@ -84,6 +103,7 @@ const FileBrowser = ({ const handleCreateFile = (file) => { updateSelectedLocation(file); setNewFileModalOpen(true); + setSaveCurrentDiagram(true); }; const handleCloseNewFileModal = () => { @@ -99,22 +119,18 @@ const FileBrowser = ({ let response; switch (data.fileType) { case "actions": - response = await axios.get( - `/bt_studio/create_action?project_name=${currentProjectname}&filename=${data.fileName}.py&template=${data.templateType}`, + await createAction( + currentProjectname, + data.fileName, + data.templateType, ); break; default: - response = await axios.get( - `/bt_studio/create_file?project_name=${currentProjectname}&location=${location}&file_name=${data.fileName}`, - ); + await createFile(currentProjectname, data.fileName, location); break; } - if (response.data.success) { - setProjectChanges(true); - fetchFileList(); // Update the file list - } else { - alert(response.data.message); - } + setProjectChanges(true); + fetchFileList(); // Update the file list } catch (error) { console.error("Error creating file:", error); } @@ -123,10 +139,12 @@ const FileBrowser = ({ ///////////////// DELETE FILES AND FOLDERS /////////////////////////////////// - const handleDeleteModal = (file_path) => { + const handleDeleteModal = (file_path, is_dir) => { if (file_path) { setDeleteEntry(file_path); + setDeleteType(is_dir); setDeleteModalOpen(true); + setSaveCurrentDiagram(true); } else { alert("No file is currently selected."); } @@ -135,26 +153,27 @@ const FileBrowser = ({ const handleCloseDeleteModal = () => { setDeleteModalOpen(false); setDeleteEntry(""); + setDeleteType(false); }; const handleSubmitDeleteModal = async () => { //currentFilename === Absolute File path if (deleteEntry) { try { - const response = await axios.get( - `/bt_studio/delete_file?project_name=${currentProjectname}&path=${deleteEntry}`, - ); - if (response.data.success) { - setProjectChanges(true); - fetchFileList(); // Update the file list - if (currentFilename === deleteEntry) { - setCurrentFilename(""); // Unset the current file - } - if (selectedEntry.path === deleteEntry) { - setSelectedEntry(null); - } + if (deleteType) { + await deleteFolder(currentProjectname, deleteEntry); } else { - alert(response.data.message); + await deleteFile(currentProjectname, deleteEntry); + } + + setProjectChanges(true); + fetchFileList(); // Update the file list + + if (currentFilename === deleteEntry) { + setCurrentFilename(""); // Unset the current file + } + if (selectedEntry.path === deleteEntry) { + setSelectedEntry(null); } } catch (error) { console.error("Error deleting file:", error); @@ -168,7 +187,8 @@ const FileBrowser = ({ const handleDeleteCurrentFile = () => { //currentFilename === Absolute File path if (currentFilename) { - handleDeleteModal(currentFilename); + handleDeleteModal(currentFilename, false); + setAutosave(false); } else { alert("No file is currently selected."); } @@ -179,6 +199,7 @@ const FileBrowser = ({ const handleCreateFolder = (file) => { updateSelectedLocation(file); setNewFolderModalOpen(true); + setSaveCurrentDiagram(true); }; const handleCloseCreateFolder = () => { @@ -189,15 +210,9 @@ const FileBrowser = ({ const handleCreateFolderSubmit = async (location, folder_name) => { if (folder_name !== "") { try { - const response = await axios.get( - `/bt_studio/create_folder?project_name=${currentProjectname}&location=${location}&folder_name=${folder_name}`, - ); - if (response.data.success) { - setProjectChanges(true); - fetchFileList(); // Update the file list - } else { - alert(response.data.message); - } + await createFolder(currentProjectname, folder_name, location); + setProjectChanges(true); + fetchFileList(); // Update the file list } catch (error) { console.error("Error creating folder:", error); } @@ -210,9 +225,14 @@ const FileBrowser = ({ if (file) { setRenameEntry(file); setRenameModalOpen(true); + setSaveCurrentDiagram(true); } else { alert("No file is currently selected."); } + + if (currentFilename === file.path) { + setForcedSaveCurrent(!forceSaveCurrent); + } }; const handleCloseRenameModal = () => { @@ -222,18 +242,19 @@ const FileBrowser = ({ const handleSubmitRenameModal = async (new_path) => { if (renameEntry) { try { - const response = await axios.get( - `/bt_studio/rename_file?project_name=${currentProjectname}&path=${renameEntry.path}&rename_to=${new_path}`, - ); - if (response.data.success) { - setProjectChanges(true); - fetchFileList(); // Update the file list - //TODO: if is a file what was renamed may need to change the path - if (currentFilename === renameEntry.name) { - setCurrentFilename(new_path); // Unset the current file - } + console.log(renameEntry); + if (renameEntry.is_dir) { + await renameFolder(currentProjectname, renameEntry.path, new_path); } else { - alert(response.data.message); + await renameFile(currentProjectname, renameEntry.path, new_path); + } + + setProjectChanges(true); + fetchFileList(); // Update the file list + + if (currentFilename === renameEntry.path) { + setAutosave(false); + setCurrentFilename(new_path); // Unset the current file } } catch (error) { console.error("Error deleting file:", error); @@ -244,11 +265,19 @@ const FileBrowser = ({ handleCloseRenameModal(); }; - const handleRenameCurrentFile = () => { - //TODO: need to obtain all file data to do this - return; + const handleRenameCurrentFile = async () => { if (currentFilename) { - handleRename(currentFilename); + setForcedSaveCurrent(!forceSaveCurrent); + const name = currentFilename.substring( + currentFilename.lastIndexOf("/") + 1, + ); + handleRename({ + is_dir: false, + name: name, + path: currentFilename, + files: [], + }); + setAutosave(false); } else { alert("No file is currently selected."); } @@ -259,6 +288,7 @@ const FileBrowser = ({ const handleUpload = (file) => { updateSelectedLocation(file); setUploadModalOpen(true); + setSaveCurrentDiagram(true); }; const handleCloseUploadModal = () => { @@ -267,42 +297,50 @@ const FileBrowser = ({ }; ///////////////// DOWNLOAD /////////////////////////////////////////////////// - const fetchDownloadData = async (file_path) => { - const api_response = await fetch("/bt_studio/download_data/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - app_name: currentProjectname, - path: file_path, - }), - }); - - if (!api_response.ok) { - var json_response = await api_response.json(); - throw new Error(json_response.message || "An error occurred."); - } + const zipFile = async (zip, file_path, file_name) => { + var content = await getFile(currentProjectname, file_path); + zip.file(file_name, content); + }; + + const zipFolder = async (zip, file) => { + const folder = zip.folder(file.name); - return api_response.blob(); + for (let index = 0; index < file.files.length; index++) { + const element = file.files[index]; + console.log(element); + if (element.is_dir) { + await zipFolder(folder, element); + } else { + await zipFile(folder, element.path, element.name); + } + } }; const handleDownload = async (file) => { if (file) { - // Get the data as a base64 blob object - const app_blob = await fetchDownloadData(file.path); - try { - const url = window.URL.createObjectURL(app_blob); - const a = document.createElement("a"); - a.style.display = "none"; - a.href = url; - a.download = `${file.name}.zip`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); + // Create the zip with the files + const zip = new JSZip(); + + if (file.is_dir) { + await zipFolder(zip, file); + } else { + await zipFile(zip, file.path, file.name); + } + + zip.generateAsync({ type: "blob" }).then(function (content) { + // Create a download link and trigger download + const url = window.URL.createObjectURL(content); + const a = document.createElement("a"); + a.style.display = "none"; + a.href = url; + a.download = `${file.name.split(".")[0]}.zip`; // Set the downloaded file's name + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); // Clean up after the download + }); } catch (error) { - console.error("Error:", error); + console.error("Error downloading file: " + error); } } }; diff --git a/frontend/src/components/file_browser/file_explorer/FileExplorer.jsx b/frontend/src/components/file_browser/file_explorer/FileExplorer.jsx index 8459087dd..51d7cdc8b 100644 --- a/frontend/src/components/file_browser/file_explorer/FileExplorer.jsx +++ b/frontend/src/components/file_browser/file_explorer/FileExplorer.jsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from "react"; -import axios from "axios"; import "./FileExplorer.css"; import TreeNode from "./TreeNode.jsx"; diff --git a/frontend/src/components/file_browser/file_explorer/MoreActionsMenu.jsx b/frontend/src/components/file_browser/file_explorer/MoreActionsMenu.jsx index f5ae1d4bd..3f756d6d4 100644 --- a/frontend/src/components/file_browser/file_explorer/MoreActionsMenu.jsx +++ b/frontend/src/components/file_browser/file_explorer/MoreActionsMenu.jsx @@ -65,7 +65,7 @@ function MoreActionsMenu({