From b6963edfcc32e13ac1da54543c5071dede0c651c Mon Sep 17 00:00:00 2001 From: Jacob Clark Date: Fri, 25 Oct 2024 11:37:29 -0700 Subject: [PATCH] wip: Added manifest file structure select --- .../shared/DatasetTreeViewRenderer/index.jsx | 177 ++++++++++++------ .../manifest/ManifestEntitySelector.jsx | 67 +++++-- .../guided-mode/guided-curate-dataset.js | 12 +- .../slices/manifestEntitySelectorSlice.js | 131 +++++++++++-- 4 files changed, 302 insertions(+), 85 deletions(-) diff --git a/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx b/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx index 3b23eede1..a27bed283 100644 --- a/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx +++ b/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx @@ -1,110 +1,169 @@ import { useState } from "react"; import { Collapse, Text, Stack, UnstyledButton } from "@mantine/core"; +import { useHover } from "@mantine/hooks"; import { IconFolder, IconFolderOpen, IconFile, IconPhoto, - IconFileTypeBmp, IconFileTypeCsv, IconFileTypeDoc, IconFileTypeDocx, IconFileTypeJpg, IconFileTypePdf, IconFileTypePng, - IconFileTypeSvg, IconFileTypeTxt, IconFileTypeXls, IconFileTypeXml, IconFileTypeZip, } from "@tabler/icons-react"; +import { getEntityForRelativePath } from "../../../stores/slices/manifestEntitySelectorSlice"; + // Constants -const folderColor = "#ADD8E6"; -const folderIconSize = 18; -const fileIconSize = 15; +const FOLDER_ICON_COLOR = "#ADD8E6"; +const FOLDER_ICON_SIZE = 18; +const FILE_ICON_SIZE = 15; + // File extension to icon map const fileIconMap = { - csv: , - xls: , - xlsx: , - txt: , - doc: , - docx: , - pdf: , - png: , - jpg: , - jpeg: , - bmp: , - svg: , - gif: , - webp: , - tiff: , - heic: , - heif: , - avif: , - jp2: , - jxr: , - wdp: , - xml: , - zip: , - rar: , + csv: , + xls: , + xlsx: , + txt: , + doc: , + docx: , + pdf: , + png: , + jpg: , + jpeg: , + xml: , + zip: , + rar: , }; -const getFileIcon = (fileName) => { + +const getFileTypeIcon = (fileName) => { const extension = fileName.split(".").pop().toLowerCase(); - return fileIconMap[extension] || ; + return fileIconMap[extension] || ; +}; + +// FileItem component +const FileItem = ({ name, content, onFileClick, getFileBackgroundColor }) => { + const filesRelativePath = content.relativePath; + const filesEntity = getEntityForRelativePath("subjects", filesRelativePath); + + const fileBackgroundColor = getFileBackgroundColor(filesRelativePath); + return ( +
onFileClick(name, content)} + > + {getFileTypeIcon(name)} + {name} +
+ ); }; -const FileView = ({ name }) => ( -
- {getFileIcon(name)} - {name} -
-); -const FolderView = ({ name, content }) => { - const [folderIsOpen, setFolderIsOpen] = useState(false); + +// FolderItem component +const FolderItem = ({ + name, + content, + onFolderClick, + onFileClick, + getFolderBackgroundColor, + getFileBackgroundColor, +}) => { + const [isOpen, setIsOpen] = useState(false); + const { hovered, ref } = useHover(); + const toggleFolder = () => { - setFolderIsOpen((prev) => !prev); + setIsOpen((prev) => !prev); }; + return ( - - {folderIsOpen ? ( - +
+ {isOpen ? ( + ) : ( - + )} - {name} - - + onFolderClick(name, content)} + > + {name} + +
+ {Object.keys(content.folders || {}).map((folderName) => ( - + ))} {Object.keys(content.files || {}).map((fileName) => ( - + ))}
); }; + // Main component -const DatasetTreeViewRenderer = ({ datasetStructure }) => { - if (!datasetStructure?.["folders"] || !datasetStructure?.["files"]) return null; - return ( +const DatasetTreeView = ({ + datasetStructure, + onFolderClick, + onFileClick, + getFolderBackgroundColor, + getFileBackgroundColor, +}) => + !datasetStructure?.folders && !datasetStructure?.files ? null : ( {Object.keys(datasetStructure.files || {}).map((fileName) => ( - + ))} {Object.keys(datasetStructure.folders || {}).map((folderName) => ( - ))} ); -}; -export default DatasetTreeViewRenderer; +export default DatasetTreeView; diff --git a/src/renderer/src/components/shared/manifest/ManifestEntitySelector.jsx b/src/renderer/src/components/shared/manifest/ManifestEntitySelector.jsx index 167012a53..0d3c863dd 100644 --- a/src/renderer/src/components/shared/manifest/ManifestEntitySelector.jsx +++ b/src/renderer/src/components/shared/manifest/ManifestEntitySelector.jsx @@ -1,27 +1,72 @@ -import { Grid, Button, Stack } from "@mantine/core"; +import { Grid, Button, Stack, Text, Paper, Divider, Group } from "@mantine/core"; import FullWidthContainer from "../../containers/FullWidthContainer"; import useGlobalStore from "../../../stores/globalStore"; import DatasetTreeViewRenderer from "../DatasetTreeViewRenderer"; +import { + setActiveEntity, + toggleRelativeFilePathForManifestEntity, +} from "../../../stores/slices/manifestEntitySelectorSlice"; const ManifestEntitySelector = () => { - const setDatasetStructureJSONObj = useGlobalStore((state) => state.setDatasetStructureJSONObj); const datasetStructureJSONObj = useGlobalStore((state) => state.datasetStructureJSONObj); + const entityList = useGlobalStore((state) => state.entityList); + const activeEntity = useGlobalStore((state) => state.activeEntity); + const entityType = useGlobalStore((state) => state.entityType); + const manifestEntityObj = useGlobalStore((state) => state.manifestEntityObj); + console.log("Manifest entity object:", JSON.stringify(manifestEntityObj, null, 2)); + const handleEntityClick = (entity) => { + setActiveEntity(entity); + }; + + const handleFolderClick = (folderName, folderContents) => { + console.log("Folder clicked:", folderName); + console.log("Folder contents:", folderContents); + console.log("Entity type:", entityType); + toggleRelativeFilePathForManifestEntity(entityType, activeEntity, folderContents.relativePath); + }; - const handleButtonClick = () => { - console.log("Button clicked!"); - const datasetStructureJSONObjCopy = JSON.parse(JSON.stringify(window.datasetStructureJSONObj)); - setDatasetStructureJSONObj(datasetStructureJSONObjCopy); + const handleFileClick = (fileName, fileContents) => { + console.log("File clicked:", fileName); + console.log("File contents:", fileContents); + console.log("Entity type:", entityType); + toggleRelativeFilePathForManifestEntity(entityType, activeEntity, fileContents.relativePath); }; return ( - - - - + + + + + Select Entity + + + + {entityList.map((entity) => ( + + ))} + + + - + + + diff --git a/src/renderer/src/scripts/guided-mode/guided-curate-dataset.js b/src/renderer/src/scripts/guided-mode/guided-curate-dataset.js index bf970ca01..7b346aef4 100644 --- a/src/renderer/src/scripts/guided-mode/guided-curate-dataset.js +++ b/src/renderer/src/scripts/guided-mode/guided-curate-dataset.js @@ -28,7 +28,6 @@ import client from "../client"; import jQuery from "jquery"; import bootstrap from "bootstrap"; import * as select2 from "select2"; // TODO: select2() -// select2() import { swalConfirmAction, swalShowError, @@ -38,7 +37,6 @@ import { } from "../utils/swal-utils"; // Import state management stores - import useGlobalStore from "../../stores/globalStore"; import { setDropdownState } from "../../stores/slices/dropDownSlice"; import { @@ -46,9 +44,13 @@ import { setGuidedDatasetName, setGuidedDatasetSubtitle, } from "../../stores/slices/guidedModeSlice"; +import { + setDatasetStructureJSONObj, + setEntityList, + setEntityType, +} from "../../stores/slices/manifestEntitySelectorSlice"; import "bootstrap-select"; -// import DragSort from '@yaireo/dragsort' import Cropper from "cropperjs"; import "jstree"; @@ -5463,6 +5465,10 @@ window.openPage = async (targetPageID) => { renderManifestCards(); } if (targetPageID === "guided-manifest-subject-entity-selector-tab") { + // + setEntityList(window.getExistingSubjectNames()); + setDatasetStructureJSONObj(window.datasetStructureJSONObj); + setEntityType("subjects"); } if (targetPageID === "guided-create-submission-metadata-tab") { diff --git a/src/renderer/src/stores/slices/manifestEntitySelectorSlice.js b/src/renderer/src/stores/slices/manifestEntitySelectorSlice.js index 4f71fb4ee..603b8f556 100644 --- a/src/renderer/src/stores/slices/manifestEntitySelectorSlice.js +++ b/src/renderer/src/stores/slices/manifestEntitySelectorSlice.js @@ -1,19 +1,126 @@ +import useGlobalStore from "../globalStore"; +import { produce } from "immer"; const initialState = { datasetStructureJSONObj: null, - manifestEntityStructure: {}, + entityList: [], + activeEntity: null, + entityType: null, + manifestEntityObj: {}, }; +// Slice now only has initial state export const manifestEntitySelectorSlice = (set) => ({ ...initialState, - resetManifestEntitySelectorState: () => { - set(() => ({ - ...initialState, - })); - }, - setDatasetStructureJSONObj: (datasetStructureJSONObj) => { - set((state) => ({ - ...state, - datasetStructureJSONObj, - })); - }, }); + +export const resetManifestEntitySelectorState = () => { + useGlobalStore.setState( + produce((state) => { + Object.assign(state, initialState); + }) + ); +}; + +export const setDatasetStructureJSONObj = (datasetStructureJSONObj) => { + // Create a deep copy of the object so that the original object is not mutated + const datasetStructureJSONObjCopy = JSON.parse(JSON.stringify(datasetStructureJSONObj)); + // Recursively add the relative paths to the datasetStructureJSONObjCopy object + // for use in the manifest entity selector component + const addRelativePaths = (obj, path = "") => { + const objFolders = Object.keys(obj.folders || {}); + const objFiles = Object.keys(obj.files || {}); + objFolders.forEach((folder) => { + const folderPath = path ? `${path}/${folder}` : folder; + obj.folders[folder].relativePath = folderPath; + addRelativePaths(obj.folders[folder], folderPath); + }); + objFiles.forEach((file) => { + const filePath = path ? `${path}/${file}` : file; + obj.files[file].relativePath = filePath; + }); + }; + addRelativePaths(datasetStructureJSONObjCopy); + console.log("Dataset structure JSON object:", datasetStructureJSONObjCopy); + useGlobalStore.setState((state) => ({ + ...state, + datasetStructureJSONObj: datasetStructureJSONObjCopy, + })); +}; + +export const setEntityList = (entityList) => { + useGlobalStore.setState((state) => ({ + ...state, + entityList, + })); +}; + +export const setActiveEntity = (activeEntity) => { + useGlobalStore.setState((state) => ({ + ...state, + activeEntity, + })); +}; + +export const setEntityType = (entityType) => { + useGlobalStore.setState((state) => ({ + ...state, + entityType, + })); +}; + +export const setManifestEntityObj = (manifestEntityObj) => { + useGlobalStore.setState((state) => ({ + ...state, + manifestEntityObj, + })); +}; + +// Function to add or update a specific value (relative path) in manifestEntityObj +export const toggleRelativeFilePathForManifestEntity = ( + entityType, + entityName, + entityRelativePath +) => { + useGlobalStore.setState( + produce((state) => { + // Check if entityType exists, if not, create it + if (!state.manifestEntityObj[entityType]) { + state.manifestEntityObj[entityType] = {}; + } + + // Check if entityName exists within the entityType, if not, create it as an array + if (!state.manifestEntityObj[entityType][entityName]) { + state.manifestEntityObj[entityType][entityName] = [entityRelativePath]; + } else { + // If entityName exists, check if the entityRelativePath exists in the array + // If it does, remove it, otherwise add it + const index = state.manifestEntityObj[entityType][entityName].indexOf(entityRelativePath); + if (index !== -1) { + state.manifestEntityObj[entityType][entityName].splice(index, 1); + } else { + state.manifestEntityObj[entityType][entityName].push(entityRelativePath); + } + } + + // Remove the entityRelativePath from all other entities in the same entityType + Object.keys(state.manifestEntityObj[entityType]).forEach((entity) => { + if (entity !== entityName) { + const index = state.manifestEntityObj[entityType][entity].indexOf(entityRelativePath); + if (index !== -1) { + state.manifestEntityObj[entityType][entity].splice(index, 1); + } + } + }); + }) + ); +}; + +export const getEntityForRelativePath = (entityType, relativePath) => { + const manifestEntityObj = useGlobalStore((state) => state.manifestEntityObj); + if (!manifestEntityObj[entityType]) { + return null; + } + return Object.keys(manifestEntityObj[entityType]).find((entity) => + manifestEntityObj[entityType][entity]?.includes(relativePath) + ); +};