From 2605554f22a30c3aebbf15d7d79b476e327f87d4 Mon Sep 17 00:00:00 2001 From: Jake Rosenberg Date: Mon, 22 Apr 2024 15:57:22 -0500 Subject: [PATCH] initial working pipeline steps for type Hybrid Sim (#1201) * initial working pipeline steps for type Hybrid Sim * add UI for changing project type * import fix * add selection page for publish vs amend/revise * formatting * linting * Add change_project_type function and test cases (#1214) * fixes for citations/tree view; add listings to entity view * update publication ingest to add all versions * add project selection to copy modal * linting fixes --------- Co-authored-by: Van Go <35277477+van-go@users.noreply.github.com> --- .../projects/UseValidateEntitySelection.ts | 32 ++ .../_hooks/src/datafiles/projects/index.ts | 4 + .../_hooks/src/datafiles/projects/types.ts | 22 +- .../projects/usePatchEntityMetadata.ts | 36 ++ .../projects/usePatchProjectMetadata.ts | 33 ++ .../datafiles/projects/useProjectDetail.ts | 4 +- .../datafiles/projects/useProjectPreview.tsx | 4 +- .../publications/usePublicationDetail.ts | 17 +- .../_hooks/src/datafiles/useFileListing.ts | 3 + .../src/datafiles/usePathDisplayName.ts | 2 +- .../_hooks/src/datafiles/useSelectedFiles.ts | 36 +- .../AddFileFolder/AddFileFolder.module.css | 1 - .../src/AddFileFolder/AddFileFolder.tsx | 31 +- .../DatafilesBreadcrumb.tsx | 27 +- .../CopyModal/CopyModal.module.css | 6 + .../DatafilesModal/CopyModal/CopyModal.tsx | 168 +++++++-- .../CopyModal/CopyModalProjectListing.tsx | 57 +++ .../src/DatafilesSideNav/DatafilesSideNav.tsx | 2 +- .../src/DatafilesToolbar/DatafilesToolbar.tsx | 1 + .../FileListingTable/FileListingTable.tsx | 3 + .../src/projects/BaseProjectDetails.tsx | 6 +- .../ProjectCitation/ProjectCitation.tsx | 51 +++ .../ProjectCollapser/ProjectCollapser.tsx | 10 +- .../ProjectPipeline/PipelineOrderAuthors.tsx | 166 +++++++++ .../PipelineOtherSelectFiles.tsx | 115 ++++++ .../PipelineProofreadCategories.tsx | 66 ++++ .../PipelineProofreadProjectStep.tsx | 57 +++ .../PipelineProofreadPublications.tsx | 66 ++++ .../PipelineSelectForPublish.tsx | 140 ++++++++ .../ProjectPipeline/PipelineSelectLicense.tsx | 288 ++++++++++++++++ .../ProjectPipeline/ProjectPipeline.tsx | 151 ++++++++ .../ProjectPreview/ProjectPreview.module.css | 3 + .../ProjectPreview/ProjectPreview.tsx | 148 ++++++-- .../ProjectTitleHeader/ProjectTitleHeader.tsx | 2 +- .../ProjectTree/ProjectTree.module.css | 3 +- .../src/projects/ProjectTree/ProjectTree.tsx | 21 +- .../datafiles/src/projects/constants.ts | 2 +- .../src/projects/forms/BaseProjectForm.tsx | 203 ++++++++--- .../projects/forms/ProjectFormDropdowns.ts | 12 + .../projects/forms/PublishableEntityForm.tsx | 34 +- .../projects/forms/_fields/AuthorSelect.tsx | 32 ++ .../modules/datafiles/src/projects/index.ts | 1 + .../modals/BaseProjectUpdateModal.tsx | 8 +- .../modals/ChangeProjectTypeModal.tsx | 296 ++++++++++++++++ .../ProjectInfoStepper/ExperimentalSteps.tsx | 264 ++++++++++++++ .../ProjectInfoStepper/FieldReconSteps.tsx | 326 ++++++++++++++++++ .../ProjectInfoStepper/SimulationSteps.tsx | 262 ++++++++++++++ .../sensitiveDataContext.ts | 6 + .../modals/ProjectTypeRadioSelect.tsx | 23 ++ .../datafiles/src/projects/modals/index.ts | 1 + client/src/datafiles/datafilesRouter.tsx | 19 +- .../projects/ProjectPipelineLayout.tsx | 10 +- .../projects/ProjectPipelineSelectLayout.tsx | 110 ++++++ .../layouts/projects/ProjectPreviewLayout.tsx | 18 +- .../published/PublishedDetailLayout.tsx | 26 +- .../PublishedEntityListingLayout.tsx | 17 + .../published/PublishedFileListingLayout.tsx | 41 +++ client/src/main.tsx | 3 + client/src/styles.css | 4 + .../migration_utils/file_obj_ingest.py | 113 ++++++ .../migration_utils/graph_constructor.py | 26 +- .../migration_utils/project_db_ingest.py | 26 +- .../migration_utils/publication_transforms.py | 60 +++- .../_tests/project_meta_unit_test.py | 23 ++ .../operations/project_meta_operations.py | 14 + .../operations/project_publish_operations.py | 106 +++++- designsafe/apps/api/projects_v2/urls.py | 5 +- designsafe/apps/api/projects_v2/views.py | 99 +++++- designsafe/apps/api/publications_v2/urls.py | 7 +- designsafe/apps/api/publications_v2/views.py | 2 +- designsafe/apps/api/urls.py | 2 +- 71 files changed, 3744 insertions(+), 239 deletions(-) create mode 100644 client/modules/_hooks/src/datafiles/projects/UseValidateEntitySelection.ts create mode 100644 client/modules/_hooks/src/datafiles/projects/usePatchEntityMetadata.ts create mode 100644 client/modules/_hooks/src/datafiles/projects/usePatchProjectMetadata.ts create mode 100644 client/modules/datafiles/src/DatafilesModal/CopyModal/CopyModalProjectListing.tsx create mode 100644 client/modules/datafiles/src/projects/ProjectCitation/ProjectCitation.tsx create mode 100644 client/modules/datafiles/src/projects/ProjectPipeline/PipelineOrderAuthors.tsx create mode 100644 client/modules/datafiles/src/projects/ProjectPipeline/PipelineOtherSelectFiles.tsx create mode 100644 client/modules/datafiles/src/projects/ProjectPipeline/PipelineProofreadCategories.tsx create mode 100644 client/modules/datafiles/src/projects/ProjectPipeline/PipelineProofreadProjectStep.tsx create mode 100644 client/modules/datafiles/src/projects/ProjectPipeline/PipelineProofreadPublications.tsx create mode 100644 client/modules/datafiles/src/projects/ProjectPipeline/PipelineSelectForPublish.tsx create mode 100644 client/modules/datafiles/src/projects/ProjectPipeline/PipelineSelectLicense.tsx create mode 100644 client/modules/datafiles/src/projects/ProjectPipeline/ProjectPipeline.tsx create mode 100644 client/modules/datafiles/src/projects/forms/_fields/AuthorSelect.tsx create mode 100644 client/modules/datafiles/src/projects/modals/ChangeProjectTypeModal.tsx create mode 100644 client/modules/datafiles/src/projects/modals/ProjectInfoStepper/ExperimentalSteps.tsx create mode 100644 client/modules/datafiles/src/projects/modals/ProjectInfoStepper/FieldReconSteps.tsx create mode 100644 client/modules/datafiles/src/projects/modals/ProjectInfoStepper/SimulationSteps.tsx create mode 100644 client/modules/datafiles/src/projects/modals/ProjectInfoStepper/sensitiveDataContext.ts create mode 100644 client/modules/datafiles/src/projects/modals/ProjectTypeRadioSelect.tsx create mode 100644 client/src/datafiles/layouts/projects/ProjectPipelineSelectLayout.tsx create mode 100644 client/src/datafiles/layouts/published/PublishedEntityListingLayout.tsx create mode 100644 client/src/datafiles/layouts/published/PublishedFileListingLayout.tsx create mode 100644 designsafe/apps/api/projects_v2/migration_utils/file_obj_ingest.py diff --git a/client/modules/_hooks/src/datafiles/projects/UseValidateEntitySelection.ts b/client/modules/_hooks/src/datafiles/projects/UseValidateEntitySelection.ts new file mode 100644 index 0000000000..fad8672179 --- /dev/null +++ b/client/modules/_hooks/src/datafiles/projects/UseValidateEntitySelection.ts @@ -0,0 +1,32 @@ +import { useMutation } from '@tanstack/react-query'; +import apiClient from '../../apiClient'; + +export type TPipelineValidationResult = { + errorType: string; + name: string; + title: string; + missing: string[]; +}; + +async function validateEntitySelection( + projectId: string, + entityUuids: string[] +) { + const res = await apiClient.post<{ result: TPipelineValidationResult[] }>( + `/api/projects/v2/${projectId}/entities/validate/`, + { entityUuids } + ); + return res.data; +} + +export function useValidateEntitySelection() { + return useMutation({ + mutationFn: ({ + projectId, + entityUuids, + }: { + projectId: string; + entityUuids: string[]; + }) => validateEntitySelection(projectId, entityUuids), + }); +} diff --git a/client/modules/_hooks/src/datafiles/projects/index.ts b/client/modules/_hooks/src/datafiles/projects/index.ts index fcbb4f5691..32cdb6fa96 100644 --- a/client/modules/_hooks/src/datafiles/projects/index.ts +++ b/client/modules/_hooks/src/datafiles/projects/index.ts @@ -12,3 +12,7 @@ export { useRemoveEntityFromTree } from './useRemoveEntityFromTree'; export { useAddFileAssociation } from './useAddFileAssociation'; export { useRemoveFileAssociation } from './useRemoveFileAssociation'; export { useSetFileTags } from './useSetFileTags'; +export { usePatchEntityMetadata } from './usePatchEntityMetadata'; +export { usePatchProjectMetadata } from './usePatchProjectMetadata'; +export { useValidateEntitySelection } from './UseValidateEntitySelection'; +export type { TPipelineValidationResult } from './UseValidateEntitySelection'; diff --git a/client/modules/_hooks/src/datafiles/projects/types.ts b/client/modules/_hooks/src/datafiles/projects/types.ts index 0d1b65b6d8..7b8f0bc483 100644 --- a/client/modules/_hooks/src/datafiles/projects/types.ts +++ b/client/modules/_hooks/src/datafiles/projects/types.ts @@ -91,11 +91,14 @@ export type TBaseProjectValue = { dois: string[]; fileObjs: TFileObj[]; fileTags: TFileTag[]; + + license?: string; }; -type TEntityValue = { +export type TEntityValue = { title: string; description?: string; + projectId?: string; authors?: TProjectUser[]; fileObjs?: TFileObj[]; fileTags: TFileTag[]; @@ -116,3 +119,20 @@ export type TBaseProject = TProjectMeta & { export type TEntityMeta = TProjectMeta & { value: TEntityValue; }; + +export type TPreviewTreeData = { + name: string; + id: string; + uuid: string; + value: TEntityValue; + order: number; + children: TPreviewTreeData[]; +}; + +export type TTreeData = { + name: string; + id: string; + uuid: string; + order: number; + children: TTreeData[]; +}; diff --git a/client/modules/_hooks/src/datafiles/projects/usePatchEntityMetadata.ts b/client/modules/_hooks/src/datafiles/projects/usePatchEntityMetadata.ts new file mode 100644 index 0000000000..4a189a2012 --- /dev/null +++ b/client/modules/_hooks/src/datafiles/projects/usePatchEntityMetadata.ts @@ -0,0 +1,36 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import apiClient from '../../apiClient'; + +async function patchEntityMetadata( + entityUuid: string, + patchMetadata: Record +) { + // Replace undefined with null so that deleted values are unset instead of ignored. + Object.keys(patchMetadata).forEach((k) => { + if (patchMetadata[k] === undefined) { + patchMetadata[k] = null; + } + }); + const res = await apiClient.patch( + `/api/projects/v2/entities/${entityUuid}/`, + { patchMetadata } + ); + return res.data; +} + +export function usePatchEntityMetadata() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + entityUuid, + patchMetadata, + }: { + patchMetadata: Record; + entityUuid: string; + }) => patchEntityMetadata(entityUuid, patchMetadata), + onSuccess: () => + queryClient.invalidateQueries({ + queryKey: ['datafiles', 'projects', 'detail'], + }), + }); +} diff --git a/client/modules/_hooks/src/datafiles/projects/usePatchProjectMetadata.ts b/client/modules/_hooks/src/datafiles/projects/usePatchProjectMetadata.ts new file mode 100644 index 0000000000..8a6ad4722d --- /dev/null +++ b/client/modules/_hooks/src/datafiles/projects/usePatchProjectMetadata.ts @@ -0,0 +1,33 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import apiClient from '../../apiClient'; + +async function patchProjectMetadata( + projectId: string, + patchMetadata: Record +) { + // Replace undefined with null so that deleted values are unset instead of ignored. + Object.keys(patchMetadata).forEach((k) => { + if (patchMetadata[k] === undefined) { + patchMetadata[k] = null; + } + }); + const res = await apiClient.patch(`/api/projects/v2/${projectId}/`, { + patchMetadata, + }); + return res.data; +} + +export function usePatchProjectMetadata(projectId: string) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + patchMetadata, + }: { + patchMetadata: Record; + }) => patchProjectMetadata(projectId, patchMetadata), + onSuccess: () => + queryClient.invalidateQueries({ + queryKey: ['datafiles', 'projects', 'detail', projectId], + }), + }); +} diff --git a/client/modules/_hooks/src/datafiles/projects/useProjectDetail.ts b/client/modules/_hooks/src/datafiles/projects/useProjectDetail.ts index 3035c52762..0995284d2d 100644 --- a/client/modules/_hooks/src/datafiles/projects/useProjectDetail.ts +++ b/client/modules/_hooks/src/datafiles/projects/useProjectDetail.ts @@ -1,12 +1,12 @@ import { useQuery } from '@tanstack/react-query'; import apiClient from '../../apiClient'; -import { TBaseProject, TEntityMeta, TFileTag } from './types'; +import { TBaseProject, TEntityMeta, TFileTag, TTreeData } from './types'; import { useMemo } from 'react'; type TProjectDetailResponse = { baseProject: TBaseProject; entities: TEntityMeta[]; - tree: unknown; + tree: TTreeData; }; async function getProjectDetail({ diff --git a/client/modules/_hooks/src/datafiles/projects/useProjectPreview.tsx b/client/modules/_hooks/src/datafiles/projects/useProjectPreview.tsx index e6da9b8255..fe5598199a 100644 --- a/client/modules/_hooks/src/datafiles/projects/useProjectPreview.tsx +++ b/client/modules/_hooks/src/datafiles/projects/useProjectPreview.tsx @@ -1,11 +1,11 @@ import { useQuery } from '@tanstack/react-query'; import apiClient from '../../apiClient'; -import { TBaseProject, TEntityMeta } from './types'; +import { TBaseProject, TEntityMeta, TPreviewTreeData } from './types'; type TProjectPreviewResponse = { baseProject: TBaseProject; entities: TEntityMeta[]; - tree: unknown; + tree: TPreviewTreeData; }; async function getProjectPreview({ diff --git a/client/modules/_hooks/src/datafiles/publications/usePublicationDetail.ts b/client/modules/_hooks/src/datafiles/publications/usePublicationDetail.ts index 4209944299..0bda571e8d 100644 --- a/client/modules/_hooks/src/datafiles/publications/usePublicationDetail.ts +++ b/client/modules/_hooks/src/datafiles/publications/usePublicationDetail.ts @@ -1,9 +1,22 @@ import { useQuery } from '@tanstack/react-query'; import apiClient from '../../apiClient'; -import { TBaseProjectValue } from '../projects'; +import { TBaseProjectValue, TEntityValue } from '../projects'; + +export type TPublicationTree = { + name: string; + uuid: string; + id: string; + basePath: string; + value: T; + publicationDate: string; + status: string; + order: number; + version?: number; + children: TPublicationTree[]; +}; export type TPublicationDetailResponse = { - tree: unknown; + tree: TPublicationTree; baseProject: TBaseProjectValue; }; diff --git a/client/modules/_hooks/src/datafiles/useFileListing.ts b/client/modules/_hooks/src/datafiles/useFileListing.ts index 7d8b43ef50..a556e38668 100644 --- a/client/modules/_hooks/src/datafiles/useFileListing.ts +++ b/client/modules/_hooks/src/datafiles/useFileListing.ts @@ -45,6 +45,7 @@ type TFileListingHookArgs = { path: string; scheme: string; pageSize: number; + disabled?: boolean; }; type TFileListingPageParam = { @@ -58,6 +59,7 @@ function useFileListing({ path, scheme = 'private', pageSize = 100, + disabled = false, }: TFileListingHookArgs) { return useInfiniteQuery< FileListingResponse, @@ -78,6 +80,7 @@ function useFileListing({ signal, } ), + enabled: !disabled, getNextPageParam: (lastPage, allpages): TFileListingPageParam | null => { return lastPage.listing.length >= pageSize ? { page: allpages.length, nextPageToken: lastPage.nextPageToken } diff --git a/client/modules/_hooks/src/datafiles/usePathDisplayName.ts b/client/modules/_hooks/src/datafiles/usePathDisplayName.ts index c84b8c6cc2..da5d378a61 100644 --- a/client/modules/_hooks/src/datafiles/usePathDisplayName.ts +++ b/client/modules/_hooks/src/datafiles/usePathDisplayName.ts @@ -31,7 +31,7 @@ function _getPathDisplayName( return 'My Data'; } if (system === 'designsafe.storage.frontera.work' && path === usernamePath) { - return 'My Data (Work)'; + return 'HPC Work'; } return decodeURIComponent(path).split('/').slice(-1)[0] || 'Data Files'; diff --git a/client/modules/_hooks/src/datafiles/useSelectedFiles.ts b/client/modules/_hooks/src/datafiles/useSelectedFiles.ts index 27b5f371d9..2edab34bca 100644 --- a/client/modules/_hooks/src/datafiles/useSelectedFiles.ts +++ b/client/modules/_hooks/src/datafiles/useSelectedFiles.ts @@ -19,24 +19,48 @@ export function useSelectedFiles( const queryClient = useQueryClient(); const setSelectedFiles = useCallback( - (selection: TFileListing[]) => - queryClient.setQueryData(queryKey, selection), + (selection: TFileListing[]) => { + queryClient.setQueryData(queryKey, selection); + queryClient.invalidateQueries({ queryKey: ['rows-for-system'] }); + }, [queryKey, queryClient] ); - return { selectedFiles: selectedRowsQuery.data, setSelectedFiles }; + const unsetSelections = useCallback(() => { + queryClient.setQueriesData({ queryKey: ['selected-rows'] }, () => []); + queryClient.invalidateQueries({ queryKey: ['rows-for-system'] }); + }, [queryClient]); + + return { + selectedFiles: selectedRowsQuery.data, + setSelectedFiles, + unsetSelections, + }; } export function useSelectedFilesForSystem(api: string, system: string) { // Get all selected files matching a given system. // Used when multiple listings can be present in a single page, e.g. publications. - const queryKey = ['selected-rows', api, system]; + const queryClient = useQueryClient(); - const selections = queryClient.getQueriesData({ queryKey }); + /* + const selections = useMemo(() => { + const queryKey = ['selected-rows', api, system]; + return queryClient.getQueriesData({ queryKey }); + }, [api, system, queryClient]); + */ + + const { data: selections } = useQuery({ + queryKey: ['rows-for-system', api, system], + queryFn: () => { + const queryKey = ['selected-rows', api, system]; + return queryClient.getQueriesData({ queryKey }); + }, + }); const reducedSelections = useMemo(() => { const allSelections: TFileListing[] = []; - selections.forEach((s) => s[1] && allSelections.push(...s[1])); + (selections ?? []).forEach((s) => s[1] && allSelections.push(...s[1])); return allSelections; }, [selections]); return reducedSelections; diff --git a/client/modules/datafiles/src/AddFileFolder/AddFileFolder.module.css b/client/modules/datafiles/src/AddFileFolder/AddFileFolder.module.css index dbf8538188..0f4239d8ad 100644 --- a/client/modules/datafiles/src/AddFileFolder/AddFileFolder.module.css +++ b/client/modules/datafiles/src/AddFileFolder/AddFileFolder.module.css @@ -35,7 +35,6 @@ a.navLink:not(:global(.active)):hover > div { } .customUl { - border: 1px solid #e3e3e3; list-style-type: none; padding-left: 0; } diff --git a/client/modules/datafiles/src/AddFileFolder/AddFileFolder.tsx b/client/modules/datafiles/src/AddFileFolder/AddFileFolder.tsx index 76b4756193..e624e8cfbe 100644 --- a/client/modules/datafiles/src/AddFileFolder/AddFileFolder.tsx +++ b/client/modules/datafiles/src/AddFileFolder/AddFileFolder.tsx @@ -102,22 +102,23 @@ export const AddFileFolder: React.FC = () => { )} -
  • { - e.preventDefault(); - window.location.href = - 'https://www.designsafe-ci.org/rw/user-guides/data-transfer-guide/'; - }} - > - + +
  • diff --git a/client/modules/datafiles/src/DatafilesBreadcrumb/DatafilesBreadcrumb.tsx b/client/modules/datafiles/src/DatafilesBreadcrumb/DatafilesBreadcrumb.tsx index cc01ec4d8e..dda77dfcb4 100644 --- a/client/modules/datafiles/src/DatafilesBreadcrumb/DatafilesBreadcrumb.tsx +++ b/client/modules/datafiles/src/DatafilesBreadcrumb/DatafilesBreadcrumb.tsx @@ -36,6 +36,7 @@ export const DatafilesBreadcrumb: React.FC< baseRoute: string; systemRoot: string; systemRootAlias?: string; + skipBreadcrumbs?: number; // Number of path elements to skip when generating breadcrumbs } & BreadcrumbProps > = ({ initialBreadcrumbs, @@ -43,11 +44,14 @@ export const DatafilesBreadcrumb: React.FC< baseRoute, systemRoot, systemRootAlias, + skipBreadcrumbs, ...props }) => { const breadcrumbItems = [ ...initialBreadcrumbs, - ...getPathRoutes(baseRoute, path, systemRoot, systemRootAlias), + ...getPathRoutes(baseRoute, path, systemRoot, systemRootAlias).slice( + skipBreadcrumbs ?? 0 + ), ]; return ( @@ -67,19 +71,32 @@ function isUserHomeSystem(system: string) { } export const BaseFileListingBreadcrumb: React.FC< - { api: string; system: string; path: string } & BreadcrumbProps -> = ({ api, system, path, ...props }) => { + { + api: string; + system: string; + path: string; + systemRootAlias?: string; + initialBreadcrumbs?: { title: string; path: string }[]; + } & BreadcrumbProps +> = ({ + api, + system, + path, + systemRootAlias, + initialBreadcrumbs = [], + ...props +}) => { const { user } = useAuthenticatedUser(); return ( ); diff --git a/client/modules/datafiles/src/DatafilesModal/CopyModal/CopyModal.module.css b/client/modules/datafiles/src/DatafilesModal/CopyModal/CopyModal.module.css index 0d3f1691ea..5ceac66cca 100644 --- a/client/modules/datafiles/src/DatafilesModal/CopyModal/CopyModal.module.css +++ b/client/modules/datafiles/src/DatafilesModal/CopyModal/CopyModal.module.css @@ -19,6 +19,12 @@ height: 100%; } +.modalRightPanel { + display: flex; + flex: 1; + flex-direction: column; +} + .destFilesSection { display: flex; flex: 1; diff --git a/client/modules/datafiles/src/DatafilesModal/CopyModal/CopyModal.tsx b/client/modules/datafiles/src/DatafilesModal/CopyModal/CopyModal.tsx index ffa8e16615..95fae57d4d 100644 --- a/client/modules/datafiles/src/DatafilesModal/CopyModal/CopyModal.tsx +++ b/client/modules/datafiles/src/DatafilesModal/CopyModal/CopyModal.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { TModalChildren } from '../DatafilesModal'; -import { Button, Modal, Table } from 'antd'; +import { Button, Modal, Select, Table } from 'antd'; import { useAuthenticatedUser, useFileCopy, @@ -14,6 +14,7 @@ import { import { BaseFileListingBreadcrumb } from '../../DatafilesBreadcrumb/DatafilesBreadcrumb'; import styles from './CopyModal.module.css'; import { toBytes } from '../../FileListing/FileListing'; +import { CopyModalProjectListing } from './CopyModalProjectListing'; const SelectedFilesColumns: TFileListingColumns = [ { @@ -31,14 +32,15 @@ const DestHeaderTitle: React.FC<{ api: string; system: string; path: string; -}> = ({ api, system, path }) => { + projectId?: string; +}> = ({ api, system, path, projectId }) => { const getPathName = usePathDisplayName(); return (    - {getPathName(api, system, path)} + {projectId || getPathName(api, system, path)} ); }; @@ -48,11 +50,19 @@ function getDestFilesColumns( system: string, path: string, mutationCallback: (path: string) => void, - navCallback: (path: string) => void + navCallback: (path: string) => void, + projectId?: string ): TFileListingColumns { return [ { - title: , + title: ( + + ), dataIndex: 'name', ellipsis: true, @@ -115,12 +125,58 @@ export const CopyModal: React.FC<{ [user] ); - const [dest, setDest] = useState(defaultDestParams); + const [dest, setDest] = useState<{ + destApi: string; + destSystem: string; + destPath: string; + destProjectId?: string; + }>(defaultDestParams); + const [showProjects, setShowProjects] = useState(false); const { destApi, destSystem, destPath } = dest; useEffect(() => setDest(defaultDestParams), [isModalOpen, defaultDestParams]); + const [dropdownValue, setDropdownValue] = useState('mydata'); + const dropdownCallback = (newValue: string) => { + setDropdownValue(newValue); + switch (newValue) { + case 'mydata': + setShowProjects(false); + setDest(defaultDestParams); + break; + case 'hpcwork': + setShowProjects(false); + setDest({ + destApi: 'tapis', + destSystem: 'designsafe.storage.frontera.work', + destPath: encodeURIComponent('/' + user?.username), + }); + break; + case 'myprojects': + setShowProjects(true); + break; + default: + setShowProjects(false); + setDest(defaultDestParams); + break; + } + }; + + const onProjectSelect = (uuid: string, projectId: string) => { + setShowProjects(false); + setDest({ + destApi: 'tapis', + destSystem: `project-${uuid}`, + destPath: '', + destProjectId: projectId, + }); + }; + const navCallback = useCallback( (path: string) => { + if (path === 'PROJECT_LISTING') { + setShowProjects(true); + return; + } const newPath = path.split('/').slice(-1)[0]; setDest({ ...dest, destPath: newPath }); }, @@ -148,9 +204,17 @@ export const CopyModal: React.FC<{ destSystem, destPath, (dPath: string) => mutateCallback(dPath), - navCallback + navCallback, + dest.destProjectId ), - [navCallback, destApi, destSystem, destPath, mutateCallback] + [ + navCallback, + destApi, + destSystem, + destPath, + dest.destProjectId, + mutateCallback, + ] ); return ( @@ -174,34 +238,68 @@ export const CopyModal: React.FC<{ scroll={{ y: '100%' }} /> -
    - { - return ( - - ); - }} +
    +