diff --git a/src/pages/DandisetPage/index.tsx b/src/pages/DandisetPage/index.tsx index 67e97bd8..399a2713 100644 --- a/src/pages/DandisetPage/index.tsx +++ b/src/pages/DandisetPage/index.tsx @@ -1,6 +1,15 @@ -import { FunctionComponent, useEffect, useMemo, useState } from "react"; +import ResponsiveLayout from "@components/ResponsiveLayout"; import { formatBytes } from "@shared/util/formatBytes"; -import { useParams, useNavigate } from "react-router-dom"; +import { + FunctionComponent, + useCallback, + useEffect, + useMemo, + useState, +} from "react"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; +import DatasetWorkspace from "../common/DatasetWorkspace/DatasetWorkspace"; +import { DatasetFile } from "../common/DatasetWorkspace/plugins/pluginInterface"; import { AssetsResponseItem, DandisetSearchResultItem, @@ -21,8 +30,9 @@ const DandisetPage: FunctionComponent = ({ width, height, }) => { - const { dandisetId } = useParams(); const navigate = useNavigate(); + const { dandisetId } = useParams(); + const [searchParams] = useSearchParams(); const staging = false; const dandisetResponse: DandisetSearchResultItem | undefined | null = useQueryDandiset(dandisetId, staging); @@ -65,10 +75,196 @@ const DandisetPage: FunctionComponent = ({ } }, [dandisetId, staging]); + // interface DatasetFile { + // id: string; + // key: string; + // filename: string; + // filepath: string; + // parentId: string; + // size: number; + // directory: boolean; + // urls: string[]; + // } + + const topLevelFiles: DatasetFile[] = useMemo(() => { + if (!allAssets) return []; + const topLevelFiles: DatasetFile[] = []; + const directoriesHandled = new Set(); + allAssets.forEach((asset) => { + const parts = asset.path.split("/"); + if (parts.length === 1) { + // file + topLevelFiles.push({ + id: asset.asset_id, + key: asset.asset_id, + filename: asset.path, + filepath: asset.path, + parentId: "", + size: asset.size, + directory: false, + urls: [ + `https://api.dandiarchive.org/api/assets/${asset.asset_id}/download/`, + ], + }); + } else { + // directory + const topLevelDir = parts[0]; + if (!directoriesHandled.has(topLevelDir)) { + directoriesHandled.add(topLevelDir); + topLevelFiles.push({ + id: topLevelDir, + key: topLevelDir, + filename: topLevelDir, + filepath: topLevelDir, + parentId: "", + size: 0, + directory: true, + urls: [], + }); + } + } + }); + return topLevelFiles; + }, [allAssets]); + + const loadFileFromPath = useMemo( + () => + async ( + filePath: string, + parentId: string, + ): Promise => { + const asset = allAssets?.find((a) => a.path === filePath); + if (!asset) return null; + return { + id: asset.asset_id, + key: asset.asset_id, + filename: asset.path, + filepath: asset.path, + parentId: parentId, + size: asset.size, + directory: false, + urls: [ + `https://api.dandiarchive.org/api/assets/${asset.asset_id}/download/`, + ], + }; + }, + [allAssets], + ); + + const fetchDirectory = useMemo( + () => + async (parent: DatasetFile): Promise => { + if (!parent.directory) return []; + const newFiles: DatasetFile[] = []; + const handledSubdirectories = new Set(); + allAssets?.forEach((asset) => { + if (asset.path.startsWith(parent.filepath + "/")) { + const p = asset.path.slice(parent.filepath.length + 1); + const parts = p.split("/"); + if (parts.length === 1) { + // file + newFiles.push({ + id: asset.asset_id, + key: asset.asset_id, + filename: asset.path.split("/").pop() || "", + filepath: asset.path, + parentId: parent.id, + size: asset.size, + directory: false, + urls: [ + `https://api.dandiarchive.org/api/assets/${asset.asset_id}/download/`, + ], + }); + } else { + // directory + const subDir = parts[0]; + if (!handledSubdirectories.has(subDir)) { + handledSubdirectories.add(subDir); + newFiles.push({ + id: subDir, + key: subDir, + filename: subDir, + filepath: parent.filepath + "/" + subDir, + parentId: parent.id, + size: 0, + directory: true, + urls: [], + }); + } + } + } + }); + return newFiles; + }, + [allAssets], + ); + + const specialOpenFileHandler = useCallback( + (file: DatasetFile) => { + if (file.filepath.endsWith(".nwb")) { + navigate( + `/nwb?url=${file.urls[0]}&dandisetId=${dandisetId}&dandisetVersion=${dandisetVersionInfo?.version}`, + ); + return true; + } + return false; + }, + [dandisetId, dandisetVersionInfo, navigate], + ); + if (!dandisetResponse || !dandisetVersionInfo) { return
Loading...
; } + const initialSplitterPosition = Math.max(500, Math.min(650, (width * 2) / 5)); + const tabFilePath = searchParams.get("tab"); + + return ( + + + + + ); +}; + +type DandisetOverviewProps = { + width: number; + height: number; + dandisetVersionInfo: DandisetVersionInfo; + nwbFilesOnly: boolean; + setNwbFilesOnly: (nwbFilesOnly: boolean) => void; + incomplete: boolean; +}; + +const DandisetOverview: FunctionComponent = ({ + width, + height, + dandisetVersionInfo, + nwbFilesOnly, + setNwbFilesOnly, + incomplete, +}) => { + const navigate = useNavigate(); return (
@@ -95,15 +291,16 @@ const DandisetPage: FunctionComponent = ({ color: "#666", }} > - ID: {dandisetId} + ID: {dandisetVersionInfo.dandiset.identifier} Version: {dandisetVersionInfo.version} Created:{" "} {new Date(dandisetVersionInfo.created).toLocaleDateString()} Status: {dandisetVersionInfo.status} + View on DANDI @@ -137,7 +334,6 @@ const DandisetPage: FunctionComponent = ({ ), )}
-
= ({ dandisetVersionInfo.metadata.assetsSummary.numberOfBytes, )}
- {allAssets ? ( - - - - - - - - - - {[...allAssets] - .sort((a, b) => a.path.localeCompare(b.path)) - .map((asset) => ( - - - - - - ))} - -
- File Path - - Modified - - Size -
-
{ - if (asset.path.endsWith(".nwb")) { - navigate( - `/nwb?url=https://api.dandiarchive.org/api/assets/${asset.asset_id}/download/&dandisetId=${dandisetId}&dandisetVersion=${dandisetVersionInfo.version}`, - ); - } - }} - > - {asset.path} -
-
- {new Date(asset.modified).toLocaleDateString()} - - {formatBytes(asset.size)} -
- ) : ( -
Loading assets...
- )} +
+ {dandisetVersionInfo.metadata.assetsSummary.numberOfFiles} files +
); }; +// const DandisetPage2: FunctionComponent = ({ +// width, +// height, +// }) => { +// const { dandisetId } = useParams(); +// const navigate = useNavigate(); +// const staging = false; +// const dandisetResponse: DandisetSearchResultItem | undefined | null = +// useQueryDandiset(dandisetId, staging); + +// // todo: get dandisetVersion from the route +// const dandisetVersion = ""; + +// const dandisetVersionInfo: DandisetVersionInfo | null = +// useDandisetVersionInfo( +// dandisetId, +// dandisetVersion || "", +// staging, +// dandisetResponse || null +// ); + +// // todo: set dandisetVersion to route if not there yet + +// const [maxNumPages] = useState(1); +// const [nwbFilesOnly, setNwbFilesOnly] = useState(false); +// const { assetsResponses, incomplete } = useQueryAssets( +// dandisetId, +// maxNumPages, +// dandisetResponse || null, +// dandisetVersionInfo, +// staging, +// nwbFilesOnly +// ); +// const allAssets: AssetsResponseItem[] | null = useMemo(() => { +// if (!assetsResponses) return null; +// const rr: AssetsResponseItem[] = []; +// assetsResponses.forEach((assetsResponse) => { +// rr.push(...assetsResponse.results); +// }); +// return rr; +// }, [assetsResponses]); + +// useEffect(() => { +// if (dandisetId) { +// addRecentDandiset(dandisetId); +// } +// }, [dandisetId, staging]); + +// if (!dandisetResponse || !dandisetVersionInfo) { +// return
Loading...
; +// } + +// return ( +// +//
+//
+// navigate("/dandi")} +// style={{ +// cursor: "pointer", +// color: "#0066cc", +// fontSize: "14px", +// }} +// > +// ← Back to DANDI +// +//
+//

{dandisetVersionInfo.metadata.name}

+ +//
+ +//
+//

+// {dandisetVersionInfo.metadata.description} +//

+//
+ +//
+// Contributors: +// {dandisetVersionInfo.metadata.contributor.map( +// (contributor, index) => ( +// +// {contributor.name} +// {index < dandisetVersionInfo.metadata.contributor.length - 1 && +// ", "} +// +// ) +// )} +//
+ +//
+//
+//

+// Assets {incomplete && "(showing partial list)"} +//

+// +//
+//
+// Total files:{" "} +// {dandisetVersionInfo.metadata.assetsSummary.numberOfFiles} +// {" | "} +// Total size:{" "} +// {formatBytes( +// dandisetVersionInfo.metadata.assetsSummary.numberOfBytes +// )} +//
+// {allAssets ? ( +// +// +// +// +// +// +// +// +// +// {[...allAssets] +// .sort((a, b) => a.path.localeCompare(b.path)) +// .map((asset) => ( +// +// +// +// +// +// ))} +// +//
+// File Path +// +// Modified +// +// Size +//
+//
{ +// if (asset.path.endsWith(".nwb")) { +// navigate( +// `/nwb?url=https://api.dandiarchive.org/api/assets/${asset.asset_id}/download/&dandisetId=${dandisetId}&dandisetVersion=${dandisetVersionInfo.version}` +// ); +// } +// }} +// > +// {asset.path} +//
+//
+// {new Date(asset.modified).toLocaleDateString()} +// +// {formatBytes(asset.size)} +//
+// ) : ( +//
Loading assets...
+// )} +//
+//
+// +// ); +// }; + export default DandisetPage; diff --git a/src/pages/common/DatasetWorkspace/DatasetWorkspace.tsx b/src/pages/common/DatasetWorkspace/DatasetWorkspace.tsx index 9030b7e3..c853d60d 100644 --- a/src/pages/common/DatasetWorkspace/DatasetWorkspace.tsx +++ b/src/pages/common/DatasetWorkspace/DatasetWorkspace.tsx @@ -25,6 +25,7 @@ interface DatasetWorkspaceProps { parentId: string, ) => Promise; fetchDirectory: (file: DatasetFile) => Promise; // fetches the files in a directory + specialOpenFileHandler?: (file: DatasetFile) => boolean; } const DatasetWorkspace: FunctionComponent = ({ @@ -34,6 +35,7 @@ const DatasetWorkspace: FunctionComponent = ({ initialTab, loadFileFromPath, fetchDirectory, + specialOpenFileHandler, }) => { const [tabsState, dispatch] = useReducer(datasetWorkspaceTabsReducer, { tabs: [], @@ -59,6 +61,11 @@ const DatasetWorkspace: FunctionComponent = ({ }, [initialTab, loadFileFromPath]); const handleOpenFile = (file: DatasetFile) => { + if (specialOpenFileHandler) { + if (specialOpenFileHandler(file)) { + return; + } + } dispatch({ type: "OPEN_TAB", file }); }; diff --git a/src/pages/common/DatasetWorkspace/datasetWorkspaceTabsReducer.ts b/src/pages/common/DatasetWorkspace/datasetWorkspaceTabsReducer.ts index 6af5b194..b6814743 100644 --- a/src/pages/common/DatasetWorkspace/datasetWorkspaceTabsReducer.ts +++ b/src/pages/common/DatasetWorkspace/datasetWorkspaceTabsReducer.ts @@ -41,6 +41,11 @@ const customReducer = ( }; } + console.log( + "--------------- test", + action.file, + action.file.filename.split("/").pop() || action.file.filename, + ); const newTab: FileTab = { id: action.file.id, label: action.file.filename.split("/").pop() || action.file.filename, diff --git a/src/pages/common/DatasetWorkspace/plugins/nifti/index.ts b/src/pages/common/DatasetWorkspace/plugins/nifti/index.ts index 2704d17f..6ddb8a5c 100644 --- a/src/pages/common/DatasetWorkspace/plugins/nifti/index.ts +++ b/src/pages/common/DatasetWorkspace/plugins/nifti/index.ts @@ -3,7 +3,7 @@ import NiftiView from "./NiftiView"; const niftiPlugin: DatasetPlugin = { name: "nifti", - type: ["*.nii.gz"], // Support NIFTI files with gzip compression + type: ["*.nii", "*.nii.gz"], // Support NIFTI files with gzip compression component: NiftiView, priority: 1, // Higher priority than default plugin }; diff --git a/src/pages/common/DatasetWorkspace/plugins/text/index.ts b/src/pages/common/DatasetWorkspace/plugins/text/index.ts index d6861d82..836a1463 100644 --- a/src/pages/common/DatasetWorkspace/plugins/text/index.ts +++ b/src/pages/common/DatasetWorkspace/plugins/text/index.ts @@ -3,7 +3,7 @@ import TextFileView from "./TextFileView"; const textPlugin: DatasetPlugin = { name: "text", - type: ["*.tsv", "*.txt", "CHANGES", "README"], + type: ["*.tsv", "*.txt", "CHANGES", "README", "*.bvals", "*.bvecs"], component: TextFileView, priority: 100, };