From 2da10b2a1a091a055e20f7ce5c9b438a79cbeca4 Mon Sep 17 00:00:00 2001 From: JacobiClark Date: Tue, 21 Jan 2025 21:06:28 -0800 Subject: [PATCH] wip: Updated help text throughout new SDS 3 pages --- .../common/InstructionalTextSection/index.jsx | 28 +++++++-- .../pages/EntityDataSelector/index.jsx | 2 - .../components/pages/EntitySelector/index.jsx | 52 ++++++++++----- .../DatasetTreeViewRenderer/ContextMenu.jsx | 1 + .../shared/DatasetTreeViewRenderer/index.jsx | 63 +++++++++++++------ .../guided-mode/guided-curate-dataset.js | 33 +++++++--- .../guided_mode/guided_curate_dataset.html | 6 +- 7 files changed, 138 insertions(+), 47 deletions(-) diff --git a/src/renderer/src/components/common/InstructionalTextSection/index.jsx b/src/renderer/src/components/common/InstructionalTextSection/index.jsx index a4084c02f..7803fd30c 100644 --- a/src/renderer/src/components/common/InstructionalTextSection/index.jsx +++ b/src/renderer/src/components/common/InstructionalTextSection/index.jsx @@ -5,12 +5,28 @@ import { Text, Image } from "@mantine/core"; // Each section specifies its type (e.g., text, image) and associated data. const contentLookup = { "sub-": [ - { type: "text", data: "This is the first section for key1." }, - { type: "image", data: "https://via.placeholder.com/150" }, // Example image URL - { type: "text", data: "Here is another section for key1." }, + { + type: "text", + data: "Each subject must be uniquely identified with a subject ID. SODA provides three options for specifying subject IDs based on the number of subjects and your preference:", + }, + { + type: "text", + data: "1. Manual Entry (Recommended for fewer than 10 subjects): Manually enter subject IDs directly into the interface below. This is ideal for small datasets or if you prefer not to use automated methods.", + }, + { + type: "text", + data: "2. Spreadsheet Entry (Recommended for more than 10 subjects): Upload a spreadsheet file containing subject IDs. This method is suited for larger datasets or when you already have the subject IDs organized in a file.", + }, + { + type: "text", + data: "3. Extract from Folder Names (Recommended if you imported folders with names that subject IDs can be extracted from): Automatically generate subject IDs by extracting them from folder names. Use this method if your data is already organized into folders named after the subjects.", + }, ], "sam-": [ - { type: "text", data: "This is the first section for key2." }, + { + type: "text", + data: "If any measurements were taken from samples collected from subjects (e.g. tissue samples), you can specify the samples in the interface below.", + }, { type: "image", data: "https://via.placeholder.com/300" }, { type: "text", data: "And yet another section for key2." }, ], @@ -35,7 +51,9 @@ const InstructionalTextSection = ({ textSectionKey }) => { switch (section.type) { case "text": // Render a text section - return {section.data}; + // Use the dangerouslySetInnerHTML prop to render HTML content because the text + // may contain HTML tags (e.g., Manual) + return ; case "image": // Render an image section return {`Section; diff --git a/src/renderer/src/components/pages/EntityDataSelector/index.jsx b/src/renderer/src/components/pages/EntityDataSelector/index.jsx index d44ba13ca..0b2f1ac86 100644 --- a/src/renderer/src/components/pages/EntityDataSelector/index.jsx +++ b/src/renderer/src/components/pages/EntityDataSelector/index.jsx @@ -29,8 +29,6 @@ import { getEntityForRelativePath, } from "../../../stores/slices/datasetEntitySelectorSlice"; -import { naturalSort } from "../../shared/utils/util-functions"; - const ENTITY_PREFIXES = ["sub-", "sam-", "perf-"]; const handleEntityClick = (entity) => setActiveEntity(entity); diff --git a/src/renderer/src/components/pages/EntitySelector/index.jsx b/src/renderer/src/components/pages/EntitySelector/index.jsx index 8ea792c92..07357097e 100644 --- a/src/renderer/src/components/pages/EntitySelector/index.jsx +++ b/src/renderer/src/components/pages/EntitySelector/index.jsx @@ -23,6 +23,7 @@ import { removeEntityFromEntityList, } from "../../../stores/slices/datasetEntitySelectorSlice"; import { naturalSort } from "../../shared/utils/util-functions"; +import { swalFileListDoubleAction } from "../../../scripts/utils/swal-utils"; const EntitySelectorPage = ({ pageName, @@ -35,7 +36,7 @@ const EntitySelectorPage = ({ datasetEntityObj: state.datasetEntityObj, })); const [newEntityName, setNewEntityName] = useState(""); - const [activeTab, setActiveTab] = useState("manual"); + const [activeTab, setActiveTab] = useState("instructions"); const isNewEntityNameValid = window.evaluateStringAgainstSdsRequirements?.( newEntityName, @@ -102,22 +103,30 @@ const EntitySelectorPage = ({ - - + + + + Instructions + manual spreadsheet - folderSelect + Extract from folder names + + + Select a tab above to begin specifying {entityTypeStringPlural}. + + setNewEntityName(event.currentTarget.value)} onKeyDown={(event) => { - if (event.key === "Enter") { + if (event.which === 13) { handleAddEntity(); } }} @@ -155,16 +164,31 @@ const EntitySelectorPage = ({ { + "on-folder-click": async (folderName, folderContents, folderIsSelected) => { const childFolderNames = Object.keys(folderContents.folders); - console.log("childFolderNames: ", naturalSort(childFolderNames)); + const potentialEntities = naturalSort(childFolderNames).map( + (childFolderName) => { + const formattedName = + entityTypePrefix && !childFolderName.startsWith(entityTypePrefix) + ? `${entityTypePrefix}${childFolderName}` + : childFolderName; + return formattedName; + } + ); + + const continueWithEntityIdImport = await swalFileListDoubleAction( + potentialEntities, + `Confirm ${entityTypeStringPlural} Import`, + `The following ${entityTypeStringPlural} have been detected in the selected folder. If you proceed, they will be added to your list of ${entityTypeStringPlural}:`, + `Import Selected ${entityTypeStringPlural}`, + `Cancel Import`, + "" + ); - for (const childFolderName of naturalSort(childFolderNames)) { - const formattedName = - entityTypePrefix && !childFolderName.startsWith(entityTypePrefix) - ? `${entityTypePrefix}${childFolderName}` - : childFolderName; - addEntityToEntityList(entityType, formattedName); + if (continueWithEntityIdImport) { + for (const entityName of potentialEntities) { + addEntityToEntityList(entityType, entityName); + } } }, "folder-click-hover-text": `Import ${entityTypeStringSingular} IDs from folders in this folder`, diff --git a/src/renderer/src/components/shared/DatasetTreeViewRenderer/ContextMenu.jsx b/src/renderer/src/components/shared/DatasetTreeViewRenderer/ContextMenu.jsx index 33bb1984d..a14e9d00e 100644 --- a/src/renderer/src/components/shared/DatasetTreeViewRenderer/ContextMenu.jsx +++ b/src/renderer/src/components/shared/DatasetTreeViewRenderer/ContextMenu.jsx @@ -113,6 +113,7 @@ const ContextMenu = () => { window.electron.ipcRenderer.send("open-folders-organize-datasets-dialog", { importRelativePath: contextMenuItemData.relativePath, }); + closeContextMenu(); }} > 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 ce8d9af40..e4bd302b7 100644 --- a/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx +++ b/src/renderer/src/components/shared/DatasetTreeViewRenderer/index.jsx @@ -46,6 +46,13 @@ const FileItem = ({ name, content, onFileClick, isFileSelected, allowStructureEd const fileRelativePathEqualsContextMenuItemRelativePath = contextMenuIsOpened && contextMenuItemData?.relativePath === content.relativePath; + const fileIsSelected = isFileSelected?.(content); + + if (fileIsSelected) { + console.log("File is selected"); + console.log(content.relativePath); + } + const handleFileContextMenuOpen = (e) => { e.preventDefault(); e.stopPropagation(); @@ -100,13 +107,19 @@ const FolderItem = ({ allowStructureEditing, folderClickHoverText, }) => { - const { folderMoveModeIsActive, contextMenuItemType, contextMenuItemData, contextMenuIsOpened } = - useGlobalStore((state) => ({ - folderMoveModeIsActive: state.folderMoveModeIsActive, - contextMenuItemType: state.contextMenuItemType, - contextMenuItemData: state.contextMenuItemData, - contextMenuIsOpened: state.contextMenuIsOpened, - })); + const { + folderMoveModeIsActive, + contextMenuItemType, + contextMenuItemData, + contextMenuIsOpened, + contextMenuItemName, + } = useGlobalStore((state) => ({ + folderMoveModeIsActive: state.folderMoveModeIsActive, + contextMenuItemType: state.contextMenuItemType, + contextMenuItemData: state.contextMenuItemData, + contextMenuIsOpened: state.contextMenuIsOpened, + contextMenuItemName: state.contextMenuItemName, + })); const [isOpen, setIsOpen] = useState(false); const { hovered, ref } = useHover(); @@ -134,9 +147,11 @@ const FolderItem = ({ const folderIsEmpty = !content || (Object.keys(content.folders).length === 0 && Object.keys(content.files).length === 0); - const folderIsPassThrough = content.passThrough; - + const folderOnlyHasFolders = + Object.keys(content.folders).length > 0 && Object.keys(content.files).length === 0; + const folderOnlyHasFiles = + Object.keys(content.folders).length === 0 && Object.keys(content.files).length > 0; const folderRelativePathEqualsContextMenuItemRelativePath = contextMenuIsOpened && contextMenuItemData?.relativePath === content.relativePath; @@ -249,14 +264,26 @@ const FolderItem = ({ }; const DatasetTreeViewRenderer = ({ folderActions, fileActions, allowStructureEditing }) => { - const { renderDatasetStructureJSONObj, datasetStructureSearchFilter, folderMoveModeIsActive } = - useGlobalStore((state) => ({ - renderDatasetStructureJSONObj: state.renderDatasetStructureJSONObj, - datasetStructureSearchFilter: state.datasetStructureSearchFilter, - folderMoveModeIsActive: state.folderMoveModeIsActive, - })); + const { + renderDatasetStructureJSONObj, + datasetStructureSearchFilter, + folderMoveModeIsActive, + contextMenuItemType, + contextMenuItemData, + contextMenuIsOpened, + contextMenuItemName, + } = useGlobalStore((state) => ({ + renderDatasetStructureJSONObj: state.renderDatasetStructureJSONObj, + datasetStructureSearchFilter: state.datasetStructureSearchFilter, + folderMoveModeIsActive: state.folderMoveModeIsActive, + folderMoveModeIsActive: state.folderMoveModeIsActive, + contextMenuItemType: state.contextMenuItemType, + contextMenuItemData: state.contextMenuItemData, + contextMenuIsOpened: state.contextMenuIsOpened, + contextMenuItemName: state.contextMenuItemName, + })); - const searcDebounceTime = 300; // Set the debounce time for the search filter (in milliseconds) + const searchDebounceTime = 300; // Set the debounce time for the search filter (in milliseconds) const [inputSearchFilter, setInputSearchFilter] = useState(datasetStructureSearchFilter); // Local state for input const searchTimeoutRef = useRef(null); @@ -274,7 +301,7 @@ const DatasetTreeViewRenderer = ({ folderActions, fileActions, allowStructureEdi // Set a new timeout to update the global state searchTimeoutRef.current = setTimeout(() => { setDatasetStructureSearchFilter(value); // Update the global state after the debounce delay - }, searcDebounceTime); + }, searchDebounceTime); }; useEffect(() => { @@ -350,7 +377,7 @@ const DatasetTreeViewRenderer = ({ folderActions, fileActions, allowStructureEdi /* make A ui that allows the user to cancel the move operation */ - Select a folder to move the data to + Select a folder to move the {contextMenuItemType} '{contextMenuItemName}' to: