From eddd3242625f85bb8848c4c954f173bf3731b451 Mon Sep 17 00:00:00 2001 From: JacobiClark Date: Thu, 9 Jan 2025 17:52:23 -0800 Subject: [PATCH] wip: Added folder/file deletion --- .../components/shared/DataImporter/index.jsx | 44 ++- .../DatasetTreeViewRenderer/ContextMenu.jsx | 30 +- .../shared/DatasetTreeViewRenderer/index.jsx | 40 ++- .../scripts/organize-dataset/organizeDS.js | 6 +- src/renderer/src/scripts/others/renderer.js | 146 +++++++--- .../src/stores/slices/datasetTreeViewSlice.js | 259 ++++++------------ 6 files changed, 266 insertions(+), 259 deletions(-) diff --git a/src/renderer/src/components/shared/DataImporter/index.jsx b/src/renderer/src/components/shared/DataImporter/index.jsx index b7894980e..37dd01ba7 100644 --- a/src/renderer/src/components/shared/DataImporter/index.jsx +++ b/src/renderer/src/components/shared/DataImporter/index.jsx @@ -2,25 +2,25 @@ import { Group, Text, rem } from "@mantine/core"; import { Dropzone } from "@mantine/dropzone"; import { IconUpload, IconFile, IconX } from "@tabler/icons-react"; import FullWidthContainer from "../../containers/FullWidthContainer"; -import useGlobalStore from "../../../stores/globalStore"; import DatasetTreeViewRenderer from "../DatasetTreeViewRenderer"; const DataImporter = () => { - const allowDrop = (event) => { - event.preventDefault(); - }; + // Handles preventing default drop action + const allowDrop = (event) => event.preventDefault(); + // Handles the file drop logic const handleDrop = async (files) => { - // Create a synthetic drop event with the dropped files (digestable by the window.drop function) - const syntheticDropEvent = { - preventDefault: () => {}, - dataTransfer: { files }, // Pass dropped files directly - }; - - // Call your existing window.drop function with the constructed event + const syntheticDropEvent = createSyntheticDropEvent(files); await window.drop(syntheticDropEvent); }; + // Creates a synthetic drop event for window.drop + const createSyntheticDropEvent = (files) => ({ + preventDefault: () => {}, + dataTransfer: { files }, + }); + + // Opens the dataset dialog on click const handleClick = async (event) => { event.preventDefault(); window.electron.ipcRenderer.send("open-folders-organize-datasets-dialog"); @@ -36,22 +36,13 @@ const DataImporter = () => { > - + - + - + @@ -64,4 +55,11 @@ const DataImporter = () => { ); }; +// Helper function to generate consistent icon styles +const iconStyle = (color) => ({ + width: rem(52), + height: rem(52), + color: `var(--mantine-color-${color}-6)`, +}); + export default DataImporter; diff --git a/src/renderer/src/components/shared/DatasetTreeViewRenderer/ContextMenu.jsx b/src/renderer/src/components/shared/DatasetTreeViewRenderer/ContextMenu.jsx index 53edab012..8e84af838 100644 --- a/src/renderer/src/components/shared/DatasetTreeViewRenderer/ContextMenu.jsx +++ b/src/renderer/src/components/shared/DatasetTreeViewRenderer/ContextMenu.jsx @@ -1,14 +1,16 @@ import { Menu } from "@mantine/core"; import { useEffect, useCallback } from "react"; import useGlobalStore from "../../../stores/globalStore"; -import { - closeContextMenu, - deleteFilesByRelativePath, - setFolderMoveMode, -} from "../../../stores/slices/datasetTreeViewSlice"; +import { closeContextMenu, setFolderMoveMode } from "../../../stores/slices/datasetTreeViewSlice"; const ContextMenu = () => { - const { contextMenuIsOpened, contextMenuPosition } = useGlobalStore(); + const { + contextMenuIsOpened, + contextMenuPosition, + contextMenuItemName, + contextMenuItemType, + contextMenuItemData, + } = useGlobalStore(); const handleClickOutside = useCallback((event) => { const menuElement = document.getElementById("context-menu"); @@ -55,6 +57,7 @@ const ContextMenu = () => {
+ asdf { setFolderMoveMode(true); @@ -65,12 +68,23 @@ const ContextMenu = () => { { - console.log("foo"); + if (contextMenuItemType === "file") { + window.deleteFilesByRelativePath([contextMenuItemData.relativePath]); + } + if (contextMenuItemType === "folder") { + window.deleteFoldersByRelativePath([contextMenuItemData.relativePath]); + } + closeContextMenu(); }} > Delete - console.log("Delete")}>qwer + qwer + {contextMenuItemType === "folder" && ( + console.log("Delete")}> + Import data into {contextMenuItemName} + + )}
diff --git a/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx b/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx index 825d23d5b..97461ea1d 100644 --- a/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx +++ b/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx @@ -106,22 +106,27 @@ const FolderItem = ({ openContextMenu({ x: e.clientX, y: e.clientY }, "folder", name, structuredClone(content)); }; + const folderIsEmpty = + !content || + (Object.keys(content.folders).length === 0 && Object.keys(content.files).length === 0); + return ( - {isOpen ? ( - ) : ( - )} + {onFolderClick && !folderMoveModeIsActive && ( {name}
@@ -158,16 +164,6 @@ const FolderItem = ({ {isOpen && ( <> - {naturalSort(Object.keys(content?.files || {})).map((fileName) => ( - - ))} {naturalSort(Object.keys(content?.folders || {})).map((folderName) => ( ))} + {naturalSort(Object.keys(content?.files || {})).map((fileName) => ( + + ))} )} @@ -216,6 +222,12 @@ const DatasetTreeViewRenderer = ({ folderActions, fileActions, allowStructureEdi ); }; + const handleDeleteAllItemsClick = () => { + console.log( + 'Deleting all items containing "' + datasetStructureSearchFilter + '" in their name' + ); + }; + const renderObjIsEmpty = !renderDatasetStructureJSONObj || (Object.keys(renderDatasetStructureJSONObj?.folders).length === 0 && @@ -244,7 +256,7 @@ const DatasetTreeViewRenderer = ({ folderActions, fileActions, allowStructureEdi )} {allowStructureEditing && ( - )} diff --git a/src/renderer/src/scripts/organize-dataset/organizeDS.js b/src/renderer/src/scripts/organize-dataset/organizeDS.js index 0c2103a3c..994d46e33 100644 --- a/src/renderer/src/scripts/organize-dataset/organizeDS.js +++ b/src/renderer/src/scripts/organize-dataset/organizeDS.js @@ -583,11 +583,9 @@ window.renameFolder = ( }; window.getGlobalPath = (path) => { - let currentPath = path.value.trim(); + let currentPath = (typeof path === "string" ? path : path.value || "").trim(); let jsonPathArray = currentPath.split("/"); - return jsonPathArray.filter((el) => { - return el != ""; - }); + return jsonPathArray.filter((el) => el !== ""); }; window.getGlobalPathFromString = (pathString) => { diff --git a/src/renderer/src/scripts/others/renderer.js b/src/renderer/src/scripts/others/renderer.js index ac1d0b48d..eaaf2dcaa 100644 --- a/src/renderer/src/scripts/others/renderer.js +++ b/src/renderer/src/scripts/others/renderer.js @@ -4157,10 +4157,6 @@ organizeDSaddFiles.addEventListener("click", function () { window.electron.ipcRenderer.send("open-files-organize-datasets-dialog"); }); -window.electron.ipcRenderer.on("selected-files-organize-datasets", async (event, importedFiles) => { - await addDataArrayToDatasetStructureAtPath(importedFiles); -}); - organizeDSaddFolders.addEventListener("click", function () { window.electron.ipcRenderer.send("open-folders-organize-datasets-dialog"); }); @@ -4169,8 +4165,37 @@ organizeDSaddFolders.addEventListener("click", function () { window.electron.ipcRenderer.on( "selected-folders-organize-datasets", async (event, importedFolders) => { - // Add the imported folders to the dataset structure - await addDataArrayToDatasetStructureAtPath(importedFolders); + try { + const currentFileExplorerPath = window.organizeDSglobalPath.value.trim(); + console.log("Importing folders at path", currentFileExplorerPath); + const builtDatasetStructureFromImportedFolders = + await window.buildDatasetStructureJsonFromImportedData( + importedFolders, + currentFileExplorerPath + ); + console.log( + "builtDatasetStructureFromImportedFolders after importing data", + builtDatasetStructureFromImportedFolders + ); + // Add the imported folders to the dataset structure + await mergeLocalAndRemoteDatasetStructure( + builtDatasetStructureFromImportedFolders, + currentFileExplorerPath + ); + + // Step 4: Update successful, show success message + window.notyf.open({ + type: "success", + message: `Data successfully imported`, + duration: 3000, + }); + /*await mergeNewDatasetStructureToExistingDatasetStructureAtPath( + builtDatasetStructureFromImportedFolders, + currentFileExplorerPath + );*/ + } catch (error) { + console.error("Error importing folders", error); + } } ); @@ -4392,8 +4417,6 @@ window.buildDatasetStructureJsonFromImportedData = async ( emptyFolders.push(pathToExplore); } else { const folderName = window.path.basename(pathToExplore); - const folderRelativePath = `${currentStructurePath}${folderName}/`; - console.log("folderRelativePath", folderRelativePath); const folderNameIsValid = window.evaluateStringAgainstSdsRequirements( folderName, "folder-or-file-name-is-valid" @@ -4533,16 +4556,6 @@ window.buildDatasetStructureJsonFromImportedData = async ( } if (problematicFolderNames.length > 0) { - /* - await swalFileListDoubleAction( - subjectsWithEmptyFolders.map((subject) => subject.subjectName), - `${highLevelFolder} data missing for some subjects`, - `The subjects below did not have folders or files containing ${highLevelFolder} data added to them:`, - `Continue without adding ${highLevelFolder} data to all subjects`, - `Finish adding ${highLevelFolder} data to subjects`, - `Would you like to continue without adding ${highLevelFolder} data to all subjects?` - ); - */ const replaceFoldersForUser = await swalFileListDoubleAction( problematicFolderNames, "Folder names not compliant with SPARC data standards detected", @@ -4619,6 +4632,53 @@ window.buildDatasetStructureJsonFromImportedData = async ( return datasetStructure; }; +window.deleteFoldersByRelativePath = (arrayOfRelativePaths) => { + for (const relativePathToDelete of arrayOfRelativePaths) { + const currentPathArray = window.getGlobalPath(relativePathToDelete); + console.log("currentPathArray", currentPathArray); + const folderToDeleteName = currentPathArray.pop(); + const parentFolder = window.getRecursivePath(currentPathArray, window.datasetStructureJSONObj); + + const folderToDeleteIsFromPennsieve = + parentFolder["folders"][folderToDeleteName]?.["type"] === "bf"; + console.log("folderToDeleteIsFromPennsieve", folderToDeleteIsFromPennsieve); + if (folderToDeleteIsFromPennsieve) { + parentFolder["folders"][folderToDeleteName]["action"].push("deleted"); + } else { + delete parentFolder["folders"][folderToDeleteName]; + } + } + + setTreeViewDatasetStructure(window.datasetStructureJSONObj); +}; + +window.deleteFilesByRelativePath = (arrayOfRelativePaths) => { + // for example primary/test/abc.txt + for (const relativePathToDelete of arrayOfRelativePaths) { + const slashDirectlyBeforeFileName = relativePathToDelete.lastIndexOf("/"); + const relativeFolderPathToFile = relativePathToDelete.slice(0, slashDirectlyBeforeFileName); + const fileNameToDelete = relativePathToDelete.slice(slashDirectlyBeforeFileName + 1); + console.log("relativeFolderPathToFile", relativeFolderPathToFile); + console.log("fileNameToDelete", fileNameToDelete); + + const currentPathArray = window.getGlobalPath(relativeFolderPathToFile); + console.log("currentPathArray", currentPathArray); + + const parentFolder = window.getRecursivePath(currentPathArray, window.datasetStructureJSONObj); + console.log("parentFolder", parentFolder); + + const fileToDeleteIsFromPennsieve = parentFolder["files"][fileNameToDelete]?.["type"] === "bf"; + + if (fileToDeleteIsFromPennsieve) { + parentFolder["files"][fileNameToDelete]["action"].push("deleted"); + } else { + delete parentFolder["files"][fileNameToDelete]; + } + } + + setTreeViewDatasetStructure(window.datasetStructureJSONObj); +}; + const mergeLocalAndRemoteDatasetStructure = async ( datasetStructureToMerge, currentFileExplorerPath @@ -4725,32 +4785,37 @@ const mergeLocalAndRemoteDatasetStructure = async ( } } } -}; -const addDataArrayToDatasetStructureAtPath = async (importedData) => { - // If no data was imported () - const numberOfItemsToImport = importedData.length; - if (numberOfItemsToImport === 0) { - window.notyf.open({ - type: "info", - message: "No folders/files were selected to import", - duration: 4000, - }); - return; - } - try { - // STEP 1: Build the JSON object from the imported data - // (This function handles bad folders/files, inaccessible folders/files, etc and returns a clean dataset structure) - const currentFileExplorerPath = window.organizeDSglobalPath.value.trim(); + console.log("Successfully merged local and remote dataset structure"); - const builtDatasetStructure = await window.buildDatasetStructureJsonFromImportedData( - importedData, - currentFileExplorerPath - ); + const currentPathArray = window.getGlobalPath(currentFileExplorerPath); // ['dataset_root', 'code'] + const nestedJsonDatasetStructure = window.getRecursivePath( + currentPathArray.slice(1), + window.datasetStructureJSONObj + ); + window.listItems(nestedJsonDatasetStructure, "#items", 500, true); + window.getInFolder( + ".single-item", + "#items", + window.organizeDSglobalPath, + window.datasetStructureJSONObj + ); + console.log("currentPathArray", currentPathArray.slice(1)); + setTreeViewDatasetStructure(window.datasetStructureJSONObj, currentPathArray.slice(1)); +}; + +const mergeNewDatasetStructureToExistingDatasetStructureAtPath = async ( + builtDatasetStructure, + relativePathToMergeObjectInto +) => { + try { + console.log("currentFileExplorerPath", currentFileExplorerPath); // Step 2: Add the imported data to the dataset structure (This function handles duplicate files, etc) await mergeLocalAndRemoteDatasetStructure(builtDatasetStructure, currentFileExplorerPath); - + console.log( + "Successfully merged the new dataset structure into the existing dataset structure" + ); // Step 3: Update the UI const currentPathArray = window.getGlobalPath(window.organizeDSglobalPath); // ['dataset_root', 'code'] const nestedJsonDatasetStructure = window.getRecursivePath( @@ -4775,6 +4840,7 @@ const addDataArrayToDatasetStructureAtPath = async (importedData) => { duration: 3000, }); } catch (error) { + console.error(error); closeFileImportLoadingSweetAlert(); window.notyf.open({ type: error.message === "Importation cancelled" ? "info-grey" : "error", @@ -4852,7 +4918,7 @@ window.drop = async (ev) => { } // Add the items to the dataset structure (This handles problematic files/folders, duplicate files etc) - await addDataArrayToDatasetStructureAtPath(accessibleItems); + await mergeNewDatasetStructureToExistingDatasetStructureAtPath(accessibleItems); }; window.irregularFolderArray = []; diff --git a/src/renderer/src/stores/slices/datasetTreeViewSlice.js b/src/renderer/src/stores/slices/datasetTreeViewSlice.js index 9d070a85f..8aa016b08 100644 --- a/src/renderer/src/stores/slices/datasetTreeViewSlice.js +++ b/src/renderer/src/stores/slices/datasetTreeViewSlice.js @@ -3,14 +3,17 @@ import { produce } from "immer"; // Initial state for managing dataset structure and filters const initialState = { - datasetStructureJSONObj: null, // Full dataset structure - renderDatasetStructureJSONObj: null, // Rendered subset of dataset structure - datasetStructureSearchFilter: "", // Current search filter text - pathToRender: [], // Path to the folder currently rendered in the tree view - contextMenuIsOpened: false, // Whether the context menu is open - contextMenuPosition: { x: 0, y: 0 }, // Position of the context menu - contextMenuItemType: null, // Type of item in the context menu - contextMenuData: null, // Data for the context menu + datasetStructureJSONObj: null, + datasetStructureJSONObjHistory: [], + datasetstructureJSONObjHistoryIndex: -1, + renderDatasetStructureJSONObj: null, + datasetStructureSearchFilter: "", + pathToRender: [], + contextMenuIsOpened: false, + contextMenuPosition: { x: 0, y: 0 }, + contextMenuItemName: null, + contextMenuItemType: null, + contextMenuItemData: null, }; // Create the dataset tree view slice for global state @@ -19,12 +22,10 @@ export const datasetTreeViewSlice = (set) => ({ }); // Traverses the dataset structure using the specified path -// Returns a reference to the nested folder at the specified path const traverseStructureByPath = (structure, pathToRender) => { - console.log("traverseStructureByPath", structure, pathToRender); let structureRef = structure; pathToRender.forEach((subFolder) => { - structureRef = structureRef.folders[subFolder]; + structureRef = structureRef?.folders?.[subFolder]; }); return structureRef; }; @@ -32,114 +33,69 @@ const traverseStructureByPath = (structure, pathToRender) => { // Determines if a folder or its subfolders/files match the search filter const folderObjMatchesSearch = (folderObj, searchFilter) => { if (!searchFilter) { - return { - matchesDirectly: true, - matchesFilesDirectly: true, - passThrough: false, - }; + return { matchesDirectly: true, matchesFilesDirectly: true, passThrough: false }; } const folderRelativePath = folderObj.relativePath.toLowerCase(); const matchesDirectly = folderRelativePath.includes(searchFilter); - - // Check if any files match the search filter - const filesMatch = Object.values(folderObj.files || {}).some((file) => + const matchesFilesDirectly = Object.values(folderObj.files || {}).some((file) => file.relativePath.toLowerCase().includes(searchFilter) ); - - // Check if any subfolders match the search filter const subfolderMatches = Object.values(folderObj.folders || {}).some((subFolder) => { const result = folderObjMatchesSearch(subFolder, searchFilter); return result.matchesDirectly || result.matchesFilesDirectly || result.passThrough; }); + const passThrough = !matchesDirectly && !matchesFilesDirectly && subfolderMatches; - // Determine if this folder is pass-through - const passThrough = !matchesDirectly && !filesMatch && subfolderMatches; - - return { - matchesDirectly, - matchesFilesDirectly: filesMatch, - passThrough, - }; + return { matchesDirectly, matchesFilesDirectly, passThrough }; }; // Filters the dataset structure based on the current search filter const filterStructure = (structure, searchFilter) => { if (!searchFilter) return structure; - const lowerCaseSearchFilter = searchFilter.toLowerCase(); - - // Recursively prunes the dataset structure to retain only matching folders/files const pruneStructure = (folderObj, searchFilter) => { - // Check if the folder matches const { matchesDirectly, matchesFilesDirectly, passThrough } = folderObjMatchesSearch( folderObj, searchFilter ); - // If it doesn't match at all, return null to remove it - if (!matchesDirectly && !matchesFilesDirectly && !passThrough) { - return null; - } - - // Set the keys in the folder object - folderObj.matchesDirectly = matchesDirectly; - folderObj.matchesFilesDirectly = matchesFilesDirectly; - folderObj.passThrough = passThrough; + if (!matchesDirectly && !matchesFilesDirectly && !passThrough) return null; - // Recursively prune subfolders - for (const subFolder of Object.keys(folderObj.folders || {})) { - const prunedSubFolder = pruneStructure(folderObj.folders[subFolder], searchFilter); - if (prunedSubFolder === null) { - delete folderObj.folders[subFolder]; + return produce(folderObj, (draft) => { + for (const subFolder in draft.folders || {}) { + if (!pruneStructure(draft.folders[subFolder], searchFilter)) { + delete draft.folders[subFolder]; + } } - } - - // Prune files that don't match the search filter - for (const fileName of Object.keys(folderObj.files || {})) { - if (!folderObj.files[fileName].relativePath.toLowerCase().includes(searchFilter)) { - delete folderObj.files[fileName]; + for (const fileName in draft.files || {}) { + if (!draft.files[fileName].relativePath.toLowerCase().includes(searchFilter)) { + delete draft.files[fileName]; + } } - } - - return folderObj; + }); }; - // Deep copy the structure to avoid in-place modification - const structureCopy = JSON.parse(JSON.stringify(structure)); - return pruneStructure(structureCopy, lowerCaseSearchFilter); + return pruneStructure(structure, searchFilter.toLowerCase()); }; -// Updates the dataset search filter and modifies the rendered structure accordingly +// Updates the dataset search filter and modifies the rendered structure export const setDatasetStructureSearchFilter = (searchFilter) => { const globalStore = useGlobalStore.getState(); - console.log("Before filter set:", globalStore); - - // Deep copy the full dataset structure to prevent mutation - const originalStructure = JSON.parse(JSON.stringify(globalStore.datasetStructureJSONObj)); - - // Get the portion of the structure to filter based on the current path + const originalStructure = globalStore.datasetStructureJSONObj; const structureToFilter = traverseStructureByPath(originalStructure, globalStore.pathToRender); - - // Apply the search filter to the relevant structure const filteredStructure = filterStructure(structureToFilter, searchFilter); - console.log("Filtered structure result:", filteredStructure); - - // Update global state with the filtered structure and search filter useGlobalStore.setState({ - ...globalStore, datasetStructureSearchFilter: searchFilter, renderDatasetStructureJSONObj: filteredStructure, }); }; -// Sets the dataset structure and renders the specified path +// Set the dataset structure and prepare it for rendering export const setTreeViewDatasetStructure = (datasetStructure, pathToRender) => { - // Deep copy the dataset structure to prevent mutation - const clonedStructure = JSON.parse(JSON.stringify(datasetStructure)); - + pathToRender = pathToRender ? pathToRender : useGlobalStore.getState().pathToRender; // Recursively adds relative paths to folders and files in the dataset structure const addRelativePaths = (obj, currentPath = []) => { for (const folderName in obj?.folders || {}) { @@ -154,137 +110,100 @@ export const setTreeViewDatasetStructure = (datasetStructure, pathToRender) => { } }; - // Add relative paths to the full dataset structure - addRelativePaths(clonedStructure); + // Ensure immutability of the updated structure + const updatedStructure = JSON.parse(JSON.stringify(datasetStructure)); // Avoid direct mutation + addRelativePaths(updatedStructure); // Add relative paths to the structure - // Update the full dataset structure and path in the global store - useGlobalStore.setState({ - datasetStructureJSONObj: clonedStructure, - pathToRender: pathToRender, - }); - - // Traverse to the folder structure to be rendered - const renderStructureRef = traverseStructureByPath(clonedStructure, pathToRender); - console.log("Render structure ref:", renderStructureRef); - - // Add relative paths for the rendered subset + // Traverse to the folder structure to be rendered and add relative paths + const renderStructureRef = traverseStructureByPath(updatedStructure, pathToRender); addRelativePaths(renderStructureRef, pathToRender); - // Update the rendered structure in the global store + // Update global store safely useGlobalStore.setState({ + datasetStructureJSONObj: updatedStructure, + pathToRender, renderDatasetStructureJSONObj: renderStructureRef, }); - - // Reset the search filter when the dataset structure is updated - setDatasetStructureSearchFilter(""); }; // Opens the context menu export const openContextMenu = (itemPosition, itemType, itemName, itemContent) => { - console.log("Opening context menu"); - console.log("itemType", itemType); - console.log("itemName", itemName); - console.log("itemContent", itemContent); - const globalStore = useGlobalStore.getState(); useGlobalStore.setState({ - ...globalStore, contextMenuIsOpened: true, contextMenuPosition: itemPosition, - contextMenuItemType: itemType, contextMenuItemName: itemName, + contextMenuItemType: itemType, contextMenuItemData: JSON.parse(JSON.stringify(itemContent)), }); }; // Closes the context menu export const closeContextMenu = () => { - const globalStore = useGlobalStore.getState(); useGlobalStore.setState({ - ...globalStore, contextMenuIsOpened: false, }); }; +// Retrieves folder structure by path export const getFolderStructureJsonByPath = (path) => { - if (typeof path === "string") { - path = path.split("/").filter(Boolean); // Split string and remove empty segments - } else if (!Array.isArray(path)) { - throw new Error("Path must be a string or an array"); - } - const globalStore = useGlobalStore.getState(); + const pathArray = typeof path === "string" ? path.split("/").filter(Boolean) : path; let structure = globalStore.datasetStructureJSONObj; - for (const folder of path) { - if (!structure.folders || !structure.folders[folder]) { - throw new Error(`Folder "${folder}" does not exist in the structure`); - } - structure = structure.folders[folder]; - } - - structure = JSON.parse(JSON.stringify(structure)); - - return structure; -}; - -export const getFileStructureJsonByPath = (path) => { - if (typeof path === "string") { - path = path.split("/").filter(Boolean); // Split string and remove empty segments - } else if (!Array.isArray(path)) { - throw new Error("Path must be a string or an array"); - } - // The file name is the last segment of the path - const fileName = path.pop(); - console.log("folder path to get file structure", path); - const folderStructure = getFolderStructureJsonByPath(path); - const fileStructure = folderStructure.files[fileName]; - console.log("fileStructure", fileStructure); - return fileStructure; -}; + pathArray.forEach((folder) => { + structure = structure?.folders?.[folder]; + if (!structure) throw new Error(`Folder "${folder}" does not exist`); + }); -export const deleteFilesByRelativePath = (relativePaths) => { - console.log("relativePaths to delete", relativePaths); - const globalStore = useGlobalStore.getState(); // Get the current state - const clonedStructure = JSON.parse(JSON.stringify(globalStore.datasetStructureJSONObj)); // Make a deep copy of the structure - console.log("clonedStructure sub-c", clonedStructure["folders"]["primary"]["folders"]["sub-c"]); - - for (const relativePath of relativePaths) { - const pathSegments = relativePath.split("/").filter(Boolean); - const fileName = pathSegments.pop(); - console.log("pathSegments", pathSegments); - console.log("fileName", fileName); - const folderJson = getFolderStructureJsonByPath(pathSegments); - - if (!folderJson.files || !folderJson.files[fileName]) { - throw new Error(`File "${fileName}" does not exist in the structure`); - } - console.log("folderJson before delete", folderJson); - delete folderJson.files[fileName]; // Delete the file - console.log("folderJson after delete", folderJson); - - // Update the folder and structure to reflect the deletion - const updatedStructure = { ...clonedStructure }; - console.log("updatedStructure", updatedStructure); - setTreeViewDatasetStructure(updatedStructure, globalStore.pathToRender); - } + return JSON.parse(JSON.stringify(structure)); // Avoid proxy-related issues }; // Folder move operations export const setFolderMoveMode = (moveMode) => { - useGlobalStore.setState((state) => { - return { - ...state, - folderMoveModeIsActive: moveMode, - }; + useGlobalStore.setState({ + folderMoveModeIsActive: moveMode, }); }; export const moveFolderToNewLocation = (targetRelativePath) => { - const { contextMenuItemName, contextMenuItemType, contextMenuItemData } = - useGlobalStore.getState(); + const globalStore = useGlobalStore.getState(); + const { contextMenuItemName, contextMenuItemType, contextMenuItemData } = globalStore; + + console.log("contextMenuItemName:", contextMenuItemName); // Debug log + console.log("contextMenuItemType:", contextMenuItemType); // Debug log + console.log("contextMenuItemData:", contextMenuItemData); // Debug log + + if (!contextMenuItemName || !contextMenuItemData) { + throw new Error("Missing contextMenuItemName or contextMenuItemData."); + } + + // Get the stringified JSON object of the target folder (where we will be moving the folder) const targetFolder = getFolderStructureJsonByPath(targetRelativePath); - console.log(targetRelativePath, targetFolder); - targetFolder.folders[contextMenuItemName] = contextMenuItemData; - deleteFilesByRelativePath([contextMenuItemData.relativePath]); - setFolderMoveMode(false); + + if (!targetFolder) { + throw new Error(`Target folder at path "${targetRelativePath}" not found.`); + } + + const originalStructure = globalStore.datasetStructureJSONObj; + const folderToDeletePathSegments = contextMenuItemData.relativePath.split("/").filter(Boolean); + console.log("folderToDeletePathSegments:", folderToDeletePathSegments); + const parentFolder = traverseStructureByPath( + originalStructure, + folderToDeletePathSegments.slice(0, -1) + ); + + if (!parentFolder || !parentFolder.folders[contextMenuItemName]) { + throw new Error(`Folder "${contextMenuItemName}" not found in the original location.`); + } + + useGlobalStore.setState( + produce((state) => { + delete parentFolder.folders[contextMenuItemName]; + targetFolder.folders[contextMenuItemName] = contextMenuItemData; + targetFolder.folders[contextMenuItemName].relativePath = + `${targetRelativePath}/${contextMenuItemName}`; + + setTreeViewDatasetStructure(state.datasetStructureJSONObj, globalStore.pathToRender); + }) + ); };