diff --git a/src/components/DatasetDetailPage/CopyButton.tsx b/src/components/DatasetDetailPage/CopyButton.tsx
new file mode 100644
index 0000000..d25d30f
--- /dev/null
+++ b/src/components/DatasetDetailPage/CopyButton.tsx
@@ -0,0 +1,55 @@
+import CheckIcon from "@mui/icons-material/Check";
+import ContentCopyIcon from "@mui/icons-material/ContentCopy";
+import { IconButton, Tooltip } from "@mui/material";
+import React from "react";
+
+const write = async (text: string) => {
+ try {
+ await navigator.clipboard.writeText(text);
+ return true;
+ } catch {
+ // Fallback
+ const ta = document.createElement("textarea");
+ ta.value = text;
+ ta.style.position = "fixed";
+ ta.style.opacity = "0";
+ document.body.appendChild(ta);
+ ta.focus();
+ ta.select();
+ const ok = document.execCommand("copy");
+ document.body.removeChild(ta);
+ return ok;
+ }
+};
+
+export default function CopyButton({
+ text,
+ title = "Copy",
+ size = "small",
+}: {
+ text: string;
+ title?: string;
+ size?: "small" | "medium" | "large";
+}) {
+ const [ok, setOk] = React.useState(false);
+
+ const onClick = async (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (await write(text)) {
+ setOk(true);
+ setTimeout(() => setOk(false), 1200);
+ }
+ };
+
+ return (
+
+
+ {ok ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/src/components/DatasetDetailPage/FileTree/FileTree.tsx b/src/components/DatasetDetailPage/FileTree/FileTree.tsx
index e3243d1..1b5b8de 100644
--- a/src/components/DatasetDetailPage/FileTree/FileTree.tsx
+++ b/src/components/DatasetDetailPage/FileTree/FileTree.tsx
@@ -1,3 +1,4 @@
+//renders the header (title, counts, total size) and the scrollable area.
import FileTreeRow from "./FileTreeRow";
import type { TreeNode } from "./types";
import FolderIcon from "@mui/icons-material/Folder";
@@ -9,7 +10,10 @@ type Props = {
tree: TreeNode[];
filesCount: number;
totalBytes: number;
- onPreview: (url: string, index: number) => void;
+ // for preview in tree row
+ onPreview: (src: string | any, index: number, isInternal?: boolean) => void;
+ getInternalByPath: (path: string) => { data: any; index: number } | undefined;
+ getJsonByPath?: (path: string) => any;
};
const formatSize = (n: number) => {
@@ -26,6 +30,8 @@ const FileTree: React.FC = ({
filesCount,
totalBytes,
onPreview,
+ getInternalByPath,
+ getJsonByPath,
}) => (
= ({
flexShrink: 0,
}}
>
-
+ {/* */}
{title}
-
+ {/*
Files: {filesCount} Size: {formatSize(totalBytes)}
-
+ */}
{tree.map((n) => (
-
+ // pass the handlePreview(onPreview = handlePreview) function to FileTreeRow
))}
diff --git a/src/components/DatasetDetailPage/FileTree/FileTreeRow.tsx b/src/components/DatasetDetailPage/FileTree/FileTreeRow.tsx
index cd2f900..d490104 100644
--- a/src/components/DatasetDetailPage/FileTree/FileTreeRow.tsx
+++ b/src/components/DatasetDetailPage/FileTree/FileTreeRow.tsx
@@ -1,30 +1,144 @@
+// for rendering the preview and download buttons in folder structure row
import type { TreeNode } from "./types";
import { formatLeafValue, isPreviewable } from "./utils";
+import CheckIcon from "@mui/icons-material/Check";
+import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import DownloadIcon from "@mui/icons-material/Download";
import ExpandLess from "@mui/icons-material/ExpandLess";
import ExpandMore from "@mui/icons-material/ExpandMore";
import FolderIcon from "@mui/icons-material/Folder";
+import FolderOpenIcon from "@mui/icons-material/FolderOpen";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
import VisibilityIcon from "@mui/icons-material/Visibility";
import { Box, Button, Collapse, Typography } from "@mui/material";
-import React from "react";
+import { Tooltip, IconButton } from "@mui/material";
+import { Colors } from "design/theme";
+import React, { useState } from "react";
+
+// show more / show less button for long string
+const LeafString: React.FC<{ value: string }> = ({ value }) => {
+ const LIMIT = 120;
+ const [expanded, setExpanded] = useState(false);
+
+ const isLong = value.length > LIMIT;
+ const display = expanded
+ ? value
+ : isLong
+ ? value.slice(0, LIMIT) + "…"
+ : value;
+
+ return (
+
+
+ {display}
+
+
+ {isLong && (
+
+ )}
+
+ );
+};
type Props = {
node: TreeNode;
level: number;
- onPreview: (url: string, index: number) => void;
+
+ // src is either an external URL(string) or the internal object
+ onPreview: (src: string | any, index: number, isInternal?: boolean) => void;
+ getInternalByPath: (path: string) => { data: any; index: number } | undefined;
+ getJsonByPath?: (path: string) => any;
+};
+
+// copy helper function
+const copyText = async (text: string) => {
+ try {
+ await navigator.clipboard.writeText(text);
+ return true;
+ } catch {
+ // fallback if the copy api not working
+ const ta = document.createElement("textarea");
+ ta.value = text;
+ ta.style.position = "fixed";
+ ta.style.opacity = "0";
+ document.body.appendChild(ta);
+ ta.select();
+ const ok = document.execCommand("copy");
+ document.body.removeChild(ta);
+ return ok;
+ }
};
-const FileTreeRow: React.FC = ({ node, level, onPreview }) => {
- const [open, setOpen] = React.useState(false);
+const FileTreeRow: React.FC = ({
+ node,
+ level,
+ onPreview,
+ getInternalByPath,
+ getJsonByPath,
+}) => {
+ const [open, setOpen] = useState(false);
+ const [copied, setCopied] = useState(false);
+ // const internal = getInternalByPath?.(node.path);
+ // const internal = getInternalByPath ? getInternalByPath(node.path) : undefined;
+ const internal = getInternalByPath(node.path);
+ const externalUrl = node.link?.url;
+
+ const handleCopy = async (e: React.MouseEvent) => {
+ e.stopPropagation(); // prevent expand/ collapse from firing when click the copy button
+ const json = getJsonByPath?.(node.path); // call getJsonByPath(node.path)
+ const asText = JSON.stringify(json, null, 2); // subtree at this row
+ if (await copyText(asText ?? "null")) {
+ // call copyText function
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1200);
+ }
+ };
if (node.kind === "folder") {
+ const isJson = /\.json$/i.test(node.name); // end with .json only
return (
<>
= ({ node, level, onPreview }) => {
onClick={() => setOpen((o) => !o)}
>
-
+ {/* */}
+ {open ? (
+
+ ) : (
+
+ )}
- {node.name}
+
+
+ {node.name}
+
+
+ {/* Actions on folder if it carries a link (from linkHere) */}
+ {node.link?.url && (
+ e.stopPropagation()} // don't toggle expand
+ >
+ }
+ onClick={() => window.open(node.link!.url, "_blank")}
+ sx={{ color: Colors.purple }}
+ >
+ Download
+
+ {isPreviewable(node.link.url) && (
+ }
+ onClick={() => onPreview(node.link!.url, node.link!.index)}
+ sx={{ color: Colors.purple }}
+ >
+ Preview
+
+ )}
+
+ )}
+
+ {/* internal preview action for folders */}
+ {internal && (
+ e.stopPropagation()}
+ >
+ }
+ onClick={() => onPreview(internal.data, internal.index, true)}
+ sx={{ color: Colors.purple }}
+ >
+ Preview
+
+
+ )}
+
+ {/* Copy subtree JSON button */}
+ e.stopPropagation()}
+ >
+
+
+ {copied ? (
+
+ ) : (
+
+ )}
+
+
+
+
{open ? : }
+ {/*timeout controls the duration of the expand/collapse animation*/}
{node.children.map((child) => (
= ({ node, level, onPreview }) => {
node={child}
level={level + 1}
onPreview={onPreview}
+ getInternalByPath={getInternalByPath}
+ getJsonByPath={getJsonByPath}
/>
))}
>
);
}
-
+ // if the node is a file
return (
-
+
= ({ node, level, onPreview }) => {
{node.name}
- {!node.link && node.value !== undefined && (
+ {!node.link &&
+ node.value !== undefined &&
+ (typeof node.value === "string" ? (
+
+ ) : (
+
+ {node.name === "_ArrayZipData_"
+ ? "[compressed data]"
+ : formatLeafValue(node.value)}
+
+ ))}
+
+ {/* {!node.link && node.value !== undefined && (
= ({ node, level, onPreview }) => {
? "[compressed data]"
: formatLeafValue(node.value)}
- )}
+ )} */}
+
+ {/* ALWAYS show copy for files, even when no external/internal */}
+
+
+
+
+ {copied ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* Placeholder to align with folder chevron */}
+
+ {(externalUrl || internal) && (
+ e.stopPropagation()}
+ >
+ {externalUrl && (
+ <>
+
+ {isPreviewable(externalUrl) && (
+
+ )}
+ >
+ )}
- {node.link?.url && (
-
-
- {isPreviewable(node.link.url) && (
+ {internal && (
}
- onClick={() => onPreview(node.link!.url, node.link!.index)}
+ onClick={(e) => {
+ e.stopPropagation();
+ onPreview(internal.data, internal.index, true);
+ }}
+ sx={{ color: Colors.purple }}
>
Preview
diff --git a/src/components/DatasetDetailPage/FileTree/types.ts b/src/components/DatasetDetailPage/FileTree/types.ts
index 211d9c5..eeb5c50 100644
--- a/src/components/DatasetDetailPage/FileTree/types.ts
+++ b/src/components/DatasetDetailPage/FileTree/types.ts
@@ -2,5 +2,11 @@ export type LinkMeta = { url: string; index: number };
// this value can be one of these types
export type TreeNode =
- | { kind: "folder"; name: string; path: string; children: TreeNode[] }
+ | {
+ kind: "folder";
+ name: string;
+ path: string;
+ children: TreeNode[];
+ link?: LinkMeta;
+ }
| { kind: "file"; name: string; path: string; value?: any; link?: LinkMeta };
diff --git a/src/components/DatasetDetailPage/FileTree/utils.ts b/src/components/DatasetDetailPage/FileTree/utils.ts
index b278647..4f0c109 100644
--- a/src/components/DatasetDetailPage/FileTree/utils.ts
+++ b/src/components/DatasetDetailPage/FileTree/utils.ts
@@ -23,10 +23,11 @@ export const formatLeafValue = (v: any): string => {
};
// ignore meta keys
-export const shouldSkipKey = (key: string) =>
- key === "_id" || key === "_rev" || key.startsWith(".");
+// export const shouldSkipKey = (key: string) =>
+// key === "_id" || key === "_rev" || key.startsWith(".");
+export const shouldSkipKey = (_key: string) => false;
-// build path -> {url, index} lookup, built from extractDataLinks function
+// build path -> {url, index} lookup, built from extractDataLinks function (return { name, size, path, url, index })
// if external link objects have {path, url, index}, build a Map for the tree
export const makeLinkMap = <
T extends { path: string; url: string; index: number }
@@ -39,37 +40,98 @@ export const makeLinkMap = <
};
// Recursively convert the dataset JSON to a file-tree
+// export const buildTreeFromDoc = (
+// doc: any,
+// linkMap: Map,
+// curPath = ""
+// ): TreeNode[] => {
+// if (!doc || typeof doc !== "object") return [];
+// const out: TreeNode[] = [];
+
+// Object.keys(doc).forEach((key) => {
+// if (shouldSkipKey(key)) return;
+
+// const val = doc[key];
+// const path = `${curPath}/${key}`;
+// const link = linkMap.get(path);
+
+// if (link) {
+// out.push({ kind: "file", name: key, path, link });
+// return;
+// }
+
+// if (val && typeof val === "object" && !Array.isArray(val)) {
+// out.push({
+// kind: "folder",
+// name: key,
+// path,
+// children: buildTreeFromDoc(val, linkMap, path),
+// });
+// return;
+// }
+
+// out.push({ kind: "file", name: key, path, value: val });
+// });
+
+// return out;
+// };
export const buildTreeFromDoc = (
doc: any,
linkMap: Map,
curPath = ""
): TreeNode[] => {
- if (!doc || typeof doc !== "object") return [];
+ if (doc === null || typeof doc !== "object") return [];
+
const out: TreeNode[] = [];
- Object.keys(doc).forEach((key) => {
- if (shouldSkipKey(key)) return;
+ if (Array.isArray(doc)) {
+ doc.forEach((item, i) => {
+ const path = `${curPath}/[${i}]`;
+ const linkHere = linkMap.get(path) || linkMap.get(`${path}/_DataLink_`);
+ // For primitive items, show "1: value" in the *name*
+ const isPrimitive =
+ item === null || ["string", "number", "boolean"].includes(typeof item);
+ const label = isPrimitive ? `${i}: ${formatLeafValue(item)}` : String(i); // objects/arrays just show "1", "2", ...
+
+ if (item && typeof item === "object" && !isPrimitive) {
+ out.push({
+ kind: "folder",
+ // name: `[${i}]`,
+ name: label,
+ path,
+ link: linkHere,
+ children: buildTreeFromDoc(item, linkMap, path),
+ });
+ } else {
+ out.push({
+ kind: "file",
+ // name: `[${i}]`,
+ name: label,
+ path,
+ link: linkHere,
+ // value: item,
+ });
+ }
+ });
+ return out;
+ }
+ Object.keys(doc).forEach((key) => {
const val = doc[key];
const path = `${curPath}/${key}`;
- const link = linkMap.get(path);
+ const linkHere = linkMap.get(path) || linkMap.get(`${path}/_DataLink_`);
- if (link) {
- out.push({ kind: "file", name: key, path, link });
- return;
- }
-
- if (val && typeof val === "object" && !Array.isArray(val)) {
+ if (val && typeof val === "object") {
out.push({
kind: "folder",
name: key,
path,
+ link: linkHere,
children: buildTreeFromDoc(val, linkMap, path),
});
- return;
+ } else {
+ out.push({ kind: "file", name: key, path, link: linkHere, value: val });
}
-
- out.push({ kind: "file", name: key, path, value: val });
});
return out;
diff --git a/src/components/DatasetDetailPage/LoadDatasetTabs.tsx b/src/components/DatasetDetailPage/LoadDatasetTabs.tsx
index 17c00e3..b480155 100644
--- a/src/components/DatasetDetailPage/LoadDatasetTabs.tsx
+++ b/src/components/DatasetDetailPage/LoadDatasetTabs.tsx
@@ -91,10 +91,10 @@ const LoadDatasetTabs: React.FC = ({
const datasetName = datasetDesc?.Name?.includes(" - ")
? datasetDesc.Name.split(" - ")[1]
: datasetDesc?.Name || datasetDocument?._id || docname;
- console.log("datasetName", datasetName);
- console.log("dbname", dbname);
- console.log("pagename", pagename);
- console.log("onekey", onekey);
+ // console.log("datasetName", datasetName);
+ // console.log("dbname", dbname);
+ // console.log("pagename", pagename);
+ // console.log("onekey", onekey);
// const datasetUrl = datasetName
// ? `${serverUrl}${dbname}/${encodeURIComponent(datasetName)}/`
// : `${serverUrl}${dbname}/`;
diff --git a/src/components/Routes.tsx b/src/components/Routes.tsx
index 8b16554..54c3e24 100644
--- a/src/components/Routes.tsx
+++ b/src/components/Routes.tsx
@@ -33,8 +33,8 @@ const Routes = () => (
{/* Dataset Details Page */}
}
- // element={}
+ // element={}
+ element={}
/>
{/* Search Page */}
diff --git a/src/pages/DatabasePage.tsx b/src/pages/DatabasePage.tsx
index 859f2ba..a266594 100644
--- a/src/pages/DatabasePage.tsx
+++ b/src/pages/DatabasePage.tsx
@@ -12,7 +12,6 @@ const DatabasePage: React.FC = () => {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const { registry } = useAppSelector(NeurojsonSelector);
- console.log("registry", registry);
useEffect(() => {
dispatch(fetchRegistry());
diff --git a/src/pages/DatasetDetailPage.tsx b/src/pages/DatasetDetailPage.tsx
index 7b47878..0999906 100644
--- a/src/pages/DatasetDetailPage.tsx
+++ b/src/pages/DatasetDetailPage.tsx
@@ -364,7 +364,7 @@ const DatasetDetailPage: React.FC = () => {
// });
// setJsonSize(minifiedBlob.size);
- const blob = new Blob([JSON.stringify(datasetDocument, null, 2)], {
+ const blob = new Blob([JSON.stringify(datasetDocument)], {
type: "application/json",
});
setJsonSize(blob.size);
diff --git a/src/pages/UpdatedDatasetDetailPage.tsx b/src/pages/UpdatedDatasetDetailPage.tsx
index 338917d..c7b2af3 100644
--- a/src/pages/UpdatedDatasetDetailPage.tsx
+++ b/src/pages/UpdatedDatasetDetailPage.tsx
@@ -1,13 +1,10 @@
import PreviewModal from "../components/PreviewModal";
import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
import DescriptionIcon from "@mui/icons-material/Description";
-import DownloadIcon from "@mui/icons-material/Download";
import ExpandLess from "@mui/icons-material/ExpandLess";
import ExpandMore from "@mui/icons-material/ExpandMore";
-import FolderIcon from "@mui/icons-material/Folder";
+// import FolderIcon from "@mui/icons-material/Folder";
import HomeIcon from "@mui/icons-material/Home";
-// import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
-// import VisibilityIcon from "@mui/icons-material/Visibility";
import {
Box,
Typography,
@@ -15,21 +12,13 @@ import {
Backdrop,
Alert,
Button,
- Card,
- CardContent,
Collapse,
} from "@mui/material";
-// new import
import FileTree from "components/DatasetDetailPage/FileTree/FileTree";
-import type {
- TreeNode,
- LinkMeta,
-} from "components/DatasetDetailPage/FileTree/types";
import {
buildTreeFromDoc,
makeLinkMap,
} from "components/DatasetDetailPage/FileTree/utils";
-// import { TextField } from "@mui/material";
import LoadDatasetTabs from "components/DatasetDetailPage/LoadDatasetTabs";
import ReadMoreText from "design/ReadMoreText";
import { Colors } from "design/theme";
@@ -38,7 +27,10 @@ import { useAppSelector } from "hooks/useAppSelector";
import React, { useEffect, useMemo, useState } from "react";
import ReactJson from "react-json-view";
import { useParams, useNavigate } from "react-router-dom";
-import { fetchDocumentDetails } from "redux/neurojson/neurojson.action";
+import {
+ fetchDocumentDetails,
+ fetchDbInfoByDatasetId,
+} from "redux/neurojson/neurojson.action";
import { NeurojsonSelector } from "redux/neurojson/neurojson.selector";
import RoutesEnum from "types/routes.enum";
@@ -55,91 +47,9 @@ interface InternalDataLink {
data: any;
index: number;
arraySize?: number[];
+ path: string; // for preview in tree row
}
-// const transformJsonForDisplay = (obj: any): any => {
-// if (typeof obj !== "object" || obj === null) return obj;
-
-// const transformed: any = Array.isArray(obj) ? [] : {};
-
-// for (const key in obj) {
-// if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
-
-// const value = obj[key];
-
-// // Match README, CHANGES, or file extensions
-// const isLongTextKey = /^(README|CHANGES)$|\.md$|\.txt$|\.m$/i.test(key);
-
-// if (typeof value === "string" && isLongTextKey) {
-// transformed[key] = `${value}`;
-// } else if (typeof value === "object") {
-// transformed[key] = transformJsonForDisplay(value);
-// } else {
-// transformed[key] = value;
-// }
-// }
-
-// return transformed;
-// };
-
-// const formatAuthorsWithDOI = (
-// authors: string[] | string,
-// doi: string
-// ): JSX.Element => {
-// let authorText = "";
-
-// if (Array.isArray(authors)) {
-// if (authors.length === 1) {
-// authorText = authors[0];
-// } else if (authors.length === 2) {
-// authorText = authors.join(", ");
-// } else {
-// authorText = `${authors.slice(0, 2).join("; ")} et al.`;
-// }
-// } else {
-// authorText = authors;
-// }
-
-// let doiUrl = "";
-// if (doi) {
-// if (/^[0-9]/.test(doi)) {
-// doiUrl = `https://doi.org/${doi}`;
-// } else if (/^doi\./.test(doi)) {
-// doiUrl = `https://${doi}`;
-// } else if (/^doi:/.test(doi)) {
-// doiUrl = doi.replace(/^doi:/, "https://doi.org/");
-// } else {
-// doiUrl = doi;
-// }
-// }
-
-// return (
-// <>
-// {authorText}
-// {doiUrl && (
-//
-// (e.currentTarget.style.textDecoration = "underline")
-// }
-// onMouseLeave={(e) => (e.currentTarget.style.textDecoration = "none")}
-// >
-// {doiUrl}
-//
-// )}
-// >
-// );
-// };
-
const UpdatedDatasetDetailPage: React.FC = () => {
const { dbName, docId } = useParams<{ dbName: string; docId: string }>();
const navigate = useNavigate();
@@ -148,28 +58,18 @@ const UpdatedDatasetDetailPage: React.FC = () => {
selectedDocument: datasetDocument,
loading,
error,
+ datasetViewInfo: dbViewInfo,
} = useAppSelector(NeurojsonSelector);
const [externalLinks, setExternalLinks] = useState([]);
const [internalLinks, setInternalLinks] = useState([]);
- // const [isExpanded, setIsExpanded] = useState(false);
const [isInternalExpanded, setIsInternalExpanded] = useState(true);
- // const [searchTerm, setSearchTerm] = useState("");
- // const [matches, setMatches] = useState([]);
- // const [highlightedIndex, setHighlightedIndex] = useState(-1);
const [downloadScript, setDownloadScript] = useState("");
const [downloadScriptSize, setDownloadScriptSize] = useState(0);
const [totalFileSize, setTotalFileSize] = useState(0);
-
const [previewIsInternal, setPreviewIsInternal] = useState(false);
const [isExternalExpanded, setIsExternalExpanded] = useState(true);
- // const [expandedPaths, setExpandedPaths] = useState([]);
- // const [originalTextMap, setOriginalTextMap] = useState<
- // Map
- // >(new Map());
- // const [jsonViewerKey, setJsonViewerKey] = useState(0);
const [jsonSize, setJsonSize] = useState(0);
- // const [transformedDataset, setTransformedDataset] = useState(null);
const [previewIndex, setPreviewIndex] = useState(0);
const aiSummary = datasetDocument?.[".datainfo"]?.AISummary ?? "";
@@ -179,39 +79,11 @@ const UpdatedDatasetDetailPage: React.FC = () => {
[datasetDocument]
);
- // 2) keep current subjects-only split, return subject objects list
- const subjectsOnly = useMemo(() => {
- const out: any = {};
- if (!datasetDocument) return out;
- Object.keys(datasetDocument).forEach((k) => {
- if (/^sub-/i.test(k)) out[k] = (datasetDocument as any)[k];
- });
- return out;
- }, [datasetDocument]);
-
- // 3) link maps
- const subjectLinks = useMemo(
- () => externalLinks.filter((l) => /^\/sub-/i.test(l.path)),
- [externalLinks]
- );
- const subjectLinkMap = useMemo(
- () => makeLinkMap(subjectLinks),
- [subjectLinks]
- );
+ const linkMap = useMemo(() => makeLinkMap(externalLinks), [externalLinks]);
- // 4) build a folder/file tree with a fallback to the WHOLE doc when no subjects exist
const treeData = useMemo(
- () =>
- hasTopLevelSubjects
- ? buildTreeFromDoc(subjectsOnly, subjectLinkMap)
- : buildTreeFromDoc(datasetDocument || {}, makeLinkMap(externalLinks)),
- [
- hasTopLevelSubjects,
- subjectsOnly,
- subjectLinkMap,
- datasetDocument,
- externalLinks,
- ]
+ () => buildTreeFromDoc(datasetDocument || {}, linkMap, ""),
+ [datasetDocument, linkMap]
);
// “rest” JSON only when we actually have subjects
@@ -225,29 +97,28 @@ const UpdatedDatasetDetailPage: React.FC = () => {
}, [datasetDocument, hasTopLevelSubjects]);
// JSON panel should always render:
- // - if we have subjects -> show "rest" (everything except sub-*)
- // - if we don't have subjects -> show the whole document
+ // - if we have subjects -> JSON show "rest" (everything except sub-*)
+ // - if we don't have subjects -> JSON show the whole document
const jsonPanelData = useMemo(
() => (hasTopLevelSubjects ? rest : datasetDocument || {}),
[hasTopLevelSubjects, rest, datasetDocument]
);
// 5) header title + counts also fall back
- const treeTitle = hasTopLevelSubjects ? "Subjects" : "Files";
-
- const { filesCount, totalBytes } = useMemo(() => {
- const group = hasTopLevelSubjects ? subjectLinks : externalLinks;
+ // const treeTitle = hasTopLevelSubjects ? "Subjects" : "Files";
+ const treeTitle = "Files";
+ const filesCount = externalLinks.length;
+ const totalBytes = useMemo(() => {
let bytes = 0;
- for (const l of group) {
+ for (const l of externalLinks) {
const m = l.url.match(/size=(\d+)/);
if (m) bytes += parseInt(m[1], 10);
}
- return { filesCount: group.length, totalBytes: bytes };
- }, [hasTopLevelSubjects, subjectLinks, externalLinks]);
+ return bytes;
+ }, [externalLinks]);
// add spinner
const [isPreviewLoading, setIsPreviewLoading] = useState(false);
- const [readyPreviewData, setReadyPreviewData] = useState(null);
const formatSize = (sizeInBytes: number): string => {
if (sizeInBytes < 1024) {
@@ -302,7 +173,6 @@ const UpdatedDatasetDetailPage: React.FC = () => {
};
traverse(obj, path);
- // return links;
const seenUrls = new Set();
const uniqueLinks = links.filter((link) => {
if (seenUrls.has(link.url)) return false;
@@ -317,56 +187,66 @@ const UpdatedDatasetDetailPage: React.FC = () => {
const internalLinks: InternalDataLink[] = [];
if (obj && typeof obj === "object") {
+ // Handle arrays so paths match the tree (/[0], /[1], …)
+ if (Array.isArray(obj)) {
+ obj.forEach((item, i) => {
+ internalLinks.push(...extractInternalData(item, `${path}/[${i}]`));
+ });
+ return internalLinks;
+ }
+
if (
obj.hasOwnProperty("MeshNode") &&
(obj.hasOwnProperty("MeshSurf") || obj.hasOwnProperty("MeshElem"))
) {
if (
- obj.MeshNode.hasOwnProperty("_ArrayZipData_") &&
+ obj.MeshNode?.hasOwnProperty("_ArrayZipData_") &&
typeof obj.MeshNode["_ArrayZipData_"] === "string"
) {
+ console.log("path", path);
internalLinks.push({
- name: `JMesh`,
+ name: "JMesh",
data: obj,
- index: internalLinks.length, // maybe can be remove
+ index: internalLinks.length,
arraySize: obj.MeshNode._ArraySize_,
+ path: `${path}/MeshNode`, // attach to the MeshNode row in the tree
});
}
} else if (obj.hasOwnProperty("NIFTIData")) {
if (
- obj.NIFTIData.hasOwnProperty("_ArrayZipData_") &&
+ obj.NIFTIData?.hasOwnProperty("_ArrayZipData_") &&
typeof obj.NIFTIData["_ArrayZipData_"] === "string"
) {
internalLinks.push({
- name: `JNIfTI`,
+ name: "JNIfTI",
data: obj,
- index: internalLinks.length, //maybe can be remove
+ index: internalLinks.length,
arraySize: obj.NIFTIData._ArraySize_,
+ path: `${path}/NIFTIData`, // attach to the NIFTIData row
});
}
} else if (
obj.hasOwnProperty("_ArraySize_") &&
- !path.match("_EnumValue_$")
+ !/_EnumValue_$/.test(path)
) {
if (
obj.hasOwnProperty("_ArrayZipData_") &&
typeof obj["_ArrayZipData_"] === "string"
) {
internalLinks.push({
- name: `JData`,
+ name: "JData",
data: obj,
- index: internalLinks.length, // maybe can be remove
+ index: internalLinks.length,
arraySize: obj._ArraySize_,
+ path, // attach to the current node
});
}
} else {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === "object") {
+ // use slash paths to match buildTreeFromDoc
internalLinks.push(
- ...extractInternalData(
- obj[key],
- `${path}.${key.replace(/\./g, "\\.")}`
- )
+ ...extractInternalData(obj[key], `${path}/${key}`)
);
}
});
@@ -380,6 +260,7 @@ const UpdatedDatasetDetailPage: React.FC = () => {
const fetchData = async () => {
if (dbName && docId) {
await dispatch(fetchDocumentDetails({ dbName, docId }));
+ await dispatch(fetchDbInfoByDatasetId({ dbName, docId }));
}
};
@@ -405,13 +286,8 @@ const UpdatedDatasetDetailPage: React.FC = () => {
})
);
- console.log(" Extracted external links:", links);
- console.log(" Extracted internal data:", internalData);
-
setExternalLinks(links);
setInternalLinks(internalData);
- // const transformed = transformJsonForDisplay(datasetDocument);
- // setTransformedDataset(transformed);
// Calculate total file size from size= query param
let total = 0;
@@ -425,7 +301,7 @@ const UpdatedDatasetDetailPage: React.FC = () => {
let totalSize = 0;
- // 1️⃣ Sum external link sizes (from URL like ...?size=12345678)
+ // 1. Sum external link sizes (from URL like ...?size=12345678)
links.forEach((link) => {
const sizeMatch = link.url.match(/size=(\d+)/);
if (sizeMatch) {
@@ -433,7 +309,7 @@ const UpdatedDatasetDetailPage: React.FC = () => {
}
});
- // 2️⃣ Estimate internal size from _ArraySize_ (assume Float32 = 4 bytes)
+ // 2. Estimate internal size from _ArraySize_ (assume Float32 = 4 bytes)
internalData.forEach((link) => {
if (link.arraySize && Array.isArray(link.arraySize)) {
const count = link.arraySize.reduce((acc, val) => acc * val, 1);
@@ -441,37 +317,17 @@ const UpdatedDatasetDetailPage: React.FC = () => {
}
});
- // setTotalFileSize(totalSize);
-
- // const minifiedBlob = new Blob([JSON.stringify(datasetDocument)], {
- // type: "application/json",
- // });
- // setJsonSize(minifiedBlob.size);
-
const blob = new Blob([JSON.stringify(datasetDocument, null, 2)], {
type: "application/json",
});
setJsonSize(blob.size);
- // // ✅ Construct download script dynamically
+ // Construct download script dynamically
let script = `curl -L --create-dirs "https://neurojson.io:7777/${dbName}/${docId}" -o "${docId}.json"\n`;
links.forEach((link) => {
const url = link.url;
- // console.log("url", url);
const match = url.match(/file=([^&]+)/);
- // console.log("match", match);
- // console.log("match[1]", match?.[1]);
- // try {
- // const decoded = match?.[1] ? decodeURIComponent(match[1]) : "N/A";
- // console.log("decode", decoded);
- // } catch (err) {
- // console.warn("⚠️ Failed to decode match[1]:", match?.[1], err);
- // }
-
- // const filename = match
- // ? decodeURIComponent(match[1])
- // : `file-${link.index}`;
const filename = match
? (() => {
@@ -482,14 +338,13 @@ const UpdatedDatasetDetailPage: React.FC = () => {
}
})()
: `file-${link.index}`;
- // console.log("filename", filename);
const outputPath = `$HOME/.neurojson/io/${dbName}/${docId}/${filename}`;
script += `curl -L --create-dirs "${url}" -o "${outputPath}"\n`;
});
setDownloadScript(script);
- // ✅ Calculate and set script size
+ // Calculate and set script size
const scriptBlob = new Blob([script], { type: "text/plain" });
setDownloadScriptSize(scriptBlob.size);
}
@@ -498,33 +353,6 @@ const UpdatedDatasetDetailPage: React.FC = () => {
const [previewOpen, setPreviewOpen] = useState(false);
const [previewDataKey, setPreviewDataKey] = useState(null);
- // useEffect(() => {
- // highlightMatches(searchTerm);
-
- // // Cleanup to reset highlights when component re-renders or unmounts
- // return () => {
- // document.querySelectorAll(".highlighted").forEach((el) => {
- // const element = el as HTMLElement;
- // const text = element.textContent || "";
- // element.innerHTML = text;
- // element.classList.remove("highlighted");
- // });
- // };
- // }, [searchTerm, datasetDocument]);
-
- // useEffect(() => {
- // if (!transformedDataset) return;
-
- // const spans = document.querySelectorAll(".string-value");
-
- // spans.forEach((el) => {
- // if (el.textContent?.includes('')) {
- // // Inject as HTML so it renders code block correctly
- // el.innerHTML = el.textContent ?? "";
- // }
- // });
- // }, [transformedDataset]);
-
const handleDownloadDataset = () => {
if (!datasetDocument) return;
const jsonData = JSON.stringify(datasetDocument);
@@ -570,27 +398,16 @@ const UpdatedDatasetDetailPage: React.FC = () => {
setPreviewDataKey(dataOrUrl);
setPreviewIsInternal(isInternal);
- // setPreviewOpen(false); // IMPORTANT: Keep modal closed for now
-
- // This callback will be triggered by the legacy script when data is ready
- // (window as any).__onPreviewReady = (decodedData: any) => {
- // console.log("✅ Data is ready! Opening modal.");
- // setReadyPreviewData(decodedData); // Store the final data for the modal
- // setIsPreviewLoading(false); // Hide the spinner
- // setPreviewOpen(true); // NOW it's time to open the modal
- // };
-
const is2DPreviewCandidate = (obj: any): boolean => {
if (typeof window !== "undefined" && (window as any).__previewType) {
- // console.log("preview type: 2d");
return (window as any).__previewType === "2d";
}
// if (window.__previewType) {
// console.log("work~~~~~~~");
// return window.__previewType === "2d";
// }
- console.log("is 2d preview candidate !== 2d");
- console.log("obj", obj);
+ // console.log("is 2d preview candidate !== 2d");
+ // console.log("obj", obj);
// if (typeof obj === "string" && obj.includes("db=optics-at-martinos")) {
// return false;
// }
@@ -600,14 +417,13 @@ const UpdatedDatasetDetailPage: React.FC = () => {
if (!obj || typeof obj !== "object") {
return false;
}
- console.log("=======after first condition");
+
if (!obj._ArrayType_ || !obj._ArraySize_ || !obj._ArrayZipData_) {
- console.log("inside second condition");
return false;
}
const dim = obj._ArraySize_;
- console.log("array.isarray(dim)", Array.isArray(dim));
- console.log("dim.length", dim.length === 1 || dim.length === 2);
+ // console.log("array.isarray(dim)", Array.isArray(dim));
+ // console.log("dim.length", dim.length === 1 || dim.length === 2);
return (
Array.isArray(dim) &&
@@ -630,7 +446,6 @@ const UpdatedDatasetDetailPage: React.FC = () => {
const extractFileName = (url: string): string => {
const match = url.match(/file=([^&]+)/);
- // return match ? decodeURIComponent(match[1]) : url;
if (match) {
// Strip any trailing query parameters
const raw = decodeURIComponent(match[1]);
@@ -648,32 +463,12 @@ const UpdatedDatasetDetailPage: React.FC = () => {
const fileName =
typeof dataOrUrl === "string" ? extractFileName(dataOrUrl) : "";
- console.log("🔍 Extracted fileName:", fileName);
+ // console.log("🔍 Extracted fileName:", fileName);
const isPreviewableFile = (fileName: string): boolean => {
return /\.(nii\.gz|jdt|jdb|bmsh|jmsh|bnii)$/i.test(fileName);
};
- console.log("🧪 isPreviewableFile:", isPreviewableFile(fileName));
-
- // test for add spinner
- // if (isInternal) {
- // if (is2DPreviewCandidate(dataOrUrl)) {
- // // inline 2D
- // window.dopreview(dataOrUrl, idx, true);
- // } else {
- // // 3D
- // window.previewdata(dataOrUrl, idx, true, []);
- // }
- // } else {
- // // external
- // window.previewdataurl(dataOrUrl, idx);
- // }
-
- // for test so command out the below
- // setPreviewIndex(idx);
- // setPreviewDataKey(dataOrUrl);
- // setPreviewIsInternal(isInternal);
- // setPreviewOpen(true);
+ // console.log("🧪 isPreviewableFile:", isPreviewableFile(fileName));
if (isInternal) {
try {
@@ -691,53 +486,73 @@ const UpdatedDatasetDetailPage: React.FC = () => {
console.log("📊 2D data → rendering inline with dopreview()");
(window as any).dopreview(dataOrUrl, idx, true);
const panel = document.getElementById("chartpanel");
- if (panel) panel.style.display = "block"; // 🔓 Show it!
- setPreviewOpen(false); // ⛔ Don't open modal
- // setPreviewLoading(false); // stop spinner
+ if (panel) panel.style.display = "block"; // Show it!
+ setPreviewOpen(false); // Don't open modal
} else {
- console.log("🎬 3D data → rendering in modal");
+ // console.log("🎬 3D data → rendering in modal");
(window as any).previewdata(dataOrUrl, idx, true, []);
- // add spinner
- // setPreviewDataKey(dataOrUrl);
- // setPreviewOpen(true);
- // setPreviewIsInternal(true);
}
} catch (err) {
console.error("❌ Error in internal preview:", err);
- // setPreviewLoading(false); // add spinner
}
} else {
- // external
- // if (/\.(nii\.gz|jdt|jdb|bmsh|jmsh|bnii)$/i.test(dataOrUrl)) {
const fileName =
typeof dataOrUrl === "string" ? extractFileName(dataOrUrl) : "";
if (isPreviewableFile(fileName)) {
(window as any).previewdataurl(dataOrUrl, idx);
- const is2D = is2DPreviewCandidate(dataOrUrl);
- const panel = document.getElementById("chartpanel");
- console.log("is2D", is2D);
- console.log("panel", panel);
-
- if (is2D) {
- console.log("📊 2D data → rendering inline with dopreview()");
- if (panel) panel.style.display = "block"; // 🔓 Show it!
- setPreviewOpen(false); // ⛔ Don't open modal
- } else {
- if (panel) panel.style.display = "none"; // 🔒 Hide chart panel on 3D external
- }
- //add spinner
- // setPreviewDataKey(dataOrUrl);
- // setPreviewOpen(true);
- // setPreviewIsInternal(false);
+ // const is2D = is2DPreviewCandidate(dataOrUrl);
+ // const panel = document.getElementById("chartpanel");
+ // console.log("is2D", is2D);
+ // console.log("panel", panel);
+
+ // if (is2D) {
+ // // console.log("📊 2D data → rendering inline with dopreview()");
+ // if (panel) panel.style.display = "block"; // Show it!
+ // setPreviewOpen(false); // Don't open modal
+ // } else {
+ // if (panel) panel.style.display = "none"; // Hide chart panel on 3D external
+ // }
} else {
console.warn("⚠️ Unsupported file format for preview:", dataOrUrl);
- // setPreviewLoading(false); // add spinner
}
}
};
+ // for preview in tree row
+
+ const internalMap = React.useMemo(() => {
+ const m = new Map();
+ for (const it of internalLinks)
+ m.set(it.path, { data: it.data, index: it.index });
+ return m;
+ }, [internalLinks]);
+
+ const getInternalByPath = (path: string) => internalMap.get(path);
+
+ // returns the subtree/primitive at that path—returning the whole document if the path is empty, or undefined if any step is invalid.
+ const getJsonByPath = React.useCallback(
+ (path: string) => {
+ if (!datasetDocument) return undefined;
+ if (!path) return datasetDocument; // root
+
+ const parts = path.split("/").filter(Boolean); // "/a/b/[0]/c" → ["a","b","[0]","c"]
+ let cur: any = datasetDocument;
+ for (const p of parts) {
+ if (/^\[\d+\]$/.test(p)) {
+ const idx = parseInt(p.slice(1, -1), 10);
+ if (!Array.isArray(cur)) return undefined;
+ cur = cur[idx];
+ } else {
+ if (cur == null || typeof cur !== "object") return undefined;
+ cur = cur[p];
+ }
+ }
+ return cur;
+ },
+ [datasetDocument]
+ );
+
const handleClosePreview = () => {
- console.log("🛑 Closing preview modal.");
setPreviewOpen(false);
setPreviewDataKey(null);
@@ -751,102 +566,12 @@ const UpdatedDatasetDetailPage: React.FC = () => {
const panel = document.getElementById("chartpanel");
if (panel) panel.style.display = "none";
- // Remove canvas children
- // const canvasDiv = document.getElementById("canvas");
- // if (canvasDiv) {
- // while (canvasDiv.firstChild) {
- // canvasDiv.removeChild(canvasDiv.firstChild);
- // }
- // }
-
// Reset Three.js global refs
window.scene = undefined;
window.camera = undefined;
window.renderer = undefined;
};
- // const handleSearch = (e: React.ChangeEvent) => {
- // setSearchTerm(e.target.value);
- // setHighlightedIndex(-1);
- // highlightMatches(e.target.value);
- // };
-
- // const highlightMatches = (keyword: string) => {
- // const spans = document.querySelectorAll(
- // ".react-json-view span.string-value, .react-json-view span.object-key"
- // );
-
- // // Clean up all existing highlights
- // spans.forEach((el) => {
- // const element = el as HTMLElement;
- // if (originalTextMap.has(element)) {
- // element.innerHTML = originalTextMap.get(element)!; // Restore original HTML
- // element.classList.remove("highlighted");
- // }
- // });
-
- // // Clear old state
- // setMatches([]);
- // setHighlightedIndex(-1);
- // setExpandedPaths([]);
- // setOriginalTextMap(new Map());
-
- // if (!keyword.trim() || keyword.length < 3) return;
-
- // const regex = new RegExp(`(${keyword})`, "gi");
- // const matchedElements: HTMLElement[] = [];
- // const matchedPaths: Set = new Set();
- // const newOriginalMap = new Map();
-
- // spans.forEach((el) => {
- // const element = el as HTMLElement;
- // const original = element.innerHTML;
- // const text = element.textContent || "";
-
- // if (text.toLowerCase().includes(keyword.toLowerCase())) {
- // newOriginalMap.set(element, original); // Store original HTML
- // const highlighted = text.replace(
- // regex,
- // `$1`
- // );
- // element.innerHTML = highlighted;
- // matchedElements.push(element);
-
- // const parent = element.closest(".variable-row");
- // const path = parent?.getAttribute("data-path");
- // if (path) matchedPaths.add(path);
- // }
- // });
-
- // // Update state
- // setOriginalTextMap(newOriginalMap);
- // setMatches(matchedElements);
- // setExpandedPaths(Array.from(matchedPaths));
- // };
-
- // const findNext = () => {
- // if (matches.length === 0) return;
-
- // setHighlightedIndex((prevIndex) => {
- // const nextIndex = (prevIndex + 1) % matches.length;
-
- // matches.forEach((match) => {
- // match
- // .querySelector("mark")
- // ?.setAttribute("style", "background: yellow; color: black;");
- // });
-
- // const current = matches[nextIndex];
- // current.scrollIntoView({ behavior: "smooth", block: "center" });
-
- // current
- // .querySelector("mark")
- // ?.setAttribute("style", "background: orange; color: black;");
-
- // return nextIndex;
- // });
- // };
-
if (loading) {
return (
{
);
}
- console.log("datasetDocument", datasetDocument);
+
const onekey = datasetDocument
? datasetDocument.hasOwnProperty("README")
? "README"
@@ -882,30 +607,6 @@ const UpdatedDatasetDetailPage: React.FC = () => {
return (
<>
- {/* 🔧 Inline CSS for string formatting */}
- {/* */}
-
- {/*
-
-
- Find Next
-
- */}
-
{
},
height: {
xs: "auto",
- md: "960px", // fixed height container
+ md: "560px", // fixed height container
},
}}
>
- {/* JSON Viewer (left panel) */}
+ {/* tree viewer (left panel) */}
{
overflow: "hidden",
}}
>
- {/* 1) SUBJECTS FILE BROWSER */}
- {hasTopLevelSubjects && (
-
- handlePreview(url, index, false)}
- />
-
- )}
-
- {/* 2) EVERYTHING ELSE AS JSON */}
-
-
+
-
- {/* = 3 ? false : 1} // 🔍 Expand during search
- style={{ fontSize: "14px", fontFamily: "monospace" }}
- /> */}
- {/* Data panels (right panel) */}
+ {/* MetaData panels (right panel) */}
{
},
display: "flex",
flexDirection: "column",
- gap: 2,
}}
>
- {/* ✅ Collapsible header */}
+
+
+ Modalities
+
+
+ {dbViewInfo?.rows?.[0]?.value?.modality?.join(", ") ??
+ "N/A"}
+
+
+
+
+
+ DOI
+
+
+ {(() => {
+ const doi =
+ datasetDocument?.["dataset_description.json"]
+ ?.DatasetDOI ||
+ datasetDocument?.["dataset_description.json"]
+ ?.ReferenceDOI;
+
+ if (!doi) return "N/A";
+
+ // Normalize into a clickable URL
+ let url = doi;
+ if (/^10\./.test(doi)) {
+ url = `https://doi.org/${doi}`;
+ } else if (/^doi:/.test(doi)) {
+ url = `https://doi.org/${doi.replace(/^doi:/, "")}`;
+ }
+
+ return (
+
+ {url}
+
+ );
+ })()}
+
+
+
+
+
+ Subjects
+
+
+ {datasetDocument?.["participants.tsv"]?.["participant_id"]
+ ?.length ?? "N/A"}
+
+
+
+
+ License
+
+
+ {datasetDocument?.["dataset_description.json"]?.License ??
+ "N/A"}
+
+
+
+
+ BIDS Version
+
+
+ {datasetDocument?.["dataset_description.json"]
+ ?.BIDSVersion ?? "N/A"}
+
+
+
+
+ References and Links
+
+
+ {Array.isArray(
+ datasetDocument?.["dataset_description.json"]
+ ?.ReferencesAndLinks
+ )
+ ? datasetDocument["dataset_description.json"]
+ .ReferencesAndLinks.length > 0
+ ? datasetDocument[
+ "dataset_description.json"
+ ].ReferencesAndLinks.join(", ")
+ : "N/A"
+ : datasetDocument?.["dataset_description.json"]
+ ?.ReferencesAndLinks ?? "N/A"}
+
+
+
+
+
+
+
+
+
+ {/* Collapsible header */}
+ setIsInternalExpanded(!isInternalExpanded)}
+ >
+
+ Internal Data ({internalLinks.length} objects)
+
+ {isInternalExpanded ? : }
+
+
+
+ {/* Scrollable area */}
+ setIsInternalExpanded(!isInternalExpanded)}
>
-
- Internal Data ({internalLinks.length} objects)
-
- {isInternalExpanded ? : }
+ {internalLinks.length > 0 ? (
+ internalLinks.map((link, index) => (
+
+
+ {link.name}{" "}
+ {link.arraySize ? `[${link.arraySize.join("x")}]` : ""}
+
+
+ handlePreview(link.data, link.index, true)
+ }
+ >
+ Preview
+
+
+ ))
+ ) : (
+
+ No internal data found.
+
+ )}
+
+
+
+ {/* Header with toggle */}
+ setIsExternalExpanded(!isExternalExpanded)}
+ >
+
+ External Data ({externalLinks.length} links)
+
+ {isExternalExpanded ? : }
+
-
- {/* ✅ Scrollable area */}
-
- {internalLinks.length > 0 ? (
- internalLinks.map((link, index) => (
+
+ {/* Scrollable card container */}
+
+ {externalLinks.length > 0 ? (
+ externalLinks.map((link, index) => {
+ const match = link.url.match(/file=([^&]+)/);
+ const fileName = match ? match[1] : "";
+ const isPreviewable =
+ /\.(nii(\.gz)?|bnii|jdt|jdb|jmsh|bmsh)$/i.test(fileName);
+
+ return (
{
- {link.name}{" "}
- {link.arraySize
- ? `[${link.arraySize.join("x")}]`
- : ""}
+ {link.name}
-
- handlePreview(link.data, link.index, true)
- }
- >
- Preview
-
-
- ))
- ) : (
-
- No internal data found.
-
- )}
-
-
-
-
- {/* ✅ Header with toggle */}
- setIsExternalExpanded(!isExternalExpanded)}
- >
-
- External Data ({externalLinks.length} links)
-
- {isExternalExpanded ? : }
-
-
-
- {/* Scrollable card container */}
-
- {externalLinks.length > 0 ? (
- externalLinks.map((link, index) => {
- const match = link.url.match(/file=([^&]+)/);
- const fileName = match ? match[1] : "";
- const isPreviewable =
- /\.(nii(\.gz)?|bnii|jdt|jdb|jmsh|bmsh)$/i.test(
- fileName
- );
-
- return (
-
-
+ window.open(link.url, "_blank")}
>
- {link.name}
-
-
+ Download
+
+ {isPreviewable && (
window.open(link.url, "_blank")}
+ onClick={() =>
+ handlePreview(link.url, link.index, false)
+ }
>
- Download
+ Preview
- {isPreviewable && (
-
- handlePreview(link.url, link.index, false)
- }
- >
- Preview
-
- )}
-
+ )}
- );
- })
- ) : (
-
- No external links found.
-
- )}
-
-
-
+
+ );
+ })
+ ) : (
+
+ No external links found.
+
+ )}
+
+
+
+
{/* */}
{
+ try {
+ const data = await NeurojsonService.getDbInfoByDatasetId(dbName, docId);
+ return { ...data, dbName, docId };
+ } catch (error: any) {
+ return rejectWithValue(error.message || "Failed to fetch dataset info.");
+ }
+ }
+);
diff --git a/src/redux/neurojson/neurojson.slice.ts b/src/redux/neurojson/neurojson.slice.ts
index b211e92..fdcd2ce 100644
--- a/src/redux/neurojson/neurojson.slice.ts
+++ b/src/redux/neurojson/neurojson.slice.ts
@@ -6,6 +6,7 @@ import {
fetchDocumentDetails,
fetchDbStats,
fetchMetadataSearchResults,
+ fetchDbInfoByDatasetId,
} from "./neurojson.action";
import { DBDatafields, INeuroJsonState } from "./types/neurojson.interface";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
@@ -24,6 +25,7 @@ const initialState: INeuroJsonState = {
dbInfo: null, // add dbInfo in neurojson.interface.ts
dbStats: null,
searchResults: null,
+ datasetViewInfo: null,
};
const neurojsonSlice = createSlice({
@@ -148,6 +150,21 @@ const neurojsonSlice = createSlice({
.addCase(fetchMetadataSearchResults.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
+ })
+ .addCase(fetchDbInfoByDatasetId.pending, (state) => {
+ state.loading = true;
+ state.error = null;
+ })
+ .addCase(
+ fetchDbInfoByDatasetId.fulfilled,
+ (state, action: PayloadAction) => {
+ state.loading = false;
+ state.datasetViewInfo = action.payload;
+ }
+ )
+ .addCase(fetchDbInfoByDatasetId.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload as string;
});
},
});
diff --git a/src/redux/neurojson/types/neurojson.interface.ts b/src/redux/neurojson/types/neurojson.interface.ts
index fd06a52..365566d 100644
--- a/src/redux/neurojson/types/neurojson.interface.ts
+++ b/src/redux/neurojson/types/neurojson.interface.ts
@@ -12,6 +12,7 @@ export interface INeuroJsonState {
dbInfo: DBParticulars | null; // add dbInfo type
dbStats: DbStatsItem[] | null; // for dbStats on landing page
searchResults: any[] | { status: string; msg: string } | null;
+ datasetViewInfo: any | null;
}
export interface DBParticulars {
diff --git a/src/services/neurojson.service.ts b/src/services/neurojson.service.ts
index d2a55d3..02aa590 100644
--- a/src/services/neurojson.service.ts
+++ b/src/services/neurojson.service.ts
@@ -100,4 +100,20 @@ export const NeurojsonService = {
return response.data;
},
+
+ getDbInfoByDatasetId: async (dbName: string, dsId: string): Promise => {
+ const response = await api.get(
+ `${baseURL}/${dbName}/_design/qq/_view/dbinfo`,
+ {
+ params: {
+ // CouchDB expects a JSON value; this produces %22ds000001%22
+ key: JSON.stringify(dsId),
+ // include_docs is optional; keep it if your view needs the full doc
+ include_docs: true,
+ // reduce: false, // uncomment if your view has a reduce function
+ },
+ }
+ );
+ return response.data;
+ },
};
diff --git a/src/utils/preview.js b/src/utils/preview.js
index 3a44b5e..912c8f8 100644
--- a/src/utils/preview.js
+++ b/src/utils/preview.js
@@ -98,15 +98,12 @@ function destroyPreview() {
function drawpreview(cfg) {
// xyzscale = undefined; // add for test
- console.log("🛠️ Rendering in drawpreview()");
- console.log("🟢 Data received:", cfg);
+ // console.log("🟢 Data received:", cfg);
initcanvas();
scene.remove.apply(scene, scene.children);
if (cfg.hasOwnProperty("Shapes")) {
- console.log("📦 Drawing Shapes...");
if (cfg.Shapes instanceof nj.NdArray) {
- console.log("🟢 Detected NumJS Array. Calling drawvolume()");
if (isWebGL2Available()) {
let box = { Grid: { Size: cfg.Shapes.shape } };
drawshape(box, 0);
@@ -136,12 +133,12 @@ function drawpreview(cfg) {
} else {
if (cfg.hasOwnProperty("MeshNode") && cfg.hasOwnProperty("MeshSurf")) {
if (cfg.MeshNode instanceof nj.NdArray) {
- console.log("✅ Rendering MeshNode & MeshSurf!");
- console.log("🔍 Rendering MeshNode & MeshSurf!");
- console.log("📌 MeshNode Data:", cfg.MeshNode);
- console.log("📌 MeshSurf Data:", cfg.MeshSurf);
- console.log("📌 MeshNode Shape:", cfg.MeshNode.shape);
- console.log("📌 MeshSurf Shape:", cfg.MeshSurf.shape);
+ // console.log("✅ Rendering MeshNode & MeshSurf!");
+ // console.log("🔍 Rendering MeshNode & MeshSurf!");
+ // console.log("📌 MeshNode Data:", cfg.MeshNode);
+ // console.log("📌 MeshSurf Data:", cfg.MeshSurf);
+ // console.log("📌 MeshNode Shape:", cfg.MeshNode.shape);
+ // console.log("📌 MeshSurf Shape:", cfg.MeshSurf.shape);
drawsurf(cfg.MeshNode, cfg.MeshSurf);
} else {
if (cfg.MeshNode.hasOwnProperty("_ArraySize_")) {
@@ -149,13 +146,13 @@ function drawpreview(cfg) {
let surfsize = cfg.MeshSurf._ArraySize_;
let jd = new jdata(cfg, {});
cfg = jd.decode().data;
- console.log("🔄 Converting MeshNode & MeshSurf to ndarrays...");
+ // console.log("🔄 Converting MeshNode & MeshSurf to ndarrays...");
drawsurf(
nj.array(cfg.MeshNode, "float32"),
nj.array(cfg.MeshSurf, "uint32")
);
} else {
- console.log("🔄 Converting MeshNode & MeshSurf from plain arrays...");
+ // console.log("🔄 Converting MeshNode & MeshSurf from plain arrays...");
drawsurf(
nj
.array(Array.from(cfg.MeshNode), "float32")
@@ -241,13 +238,13 @@ function drawpreview(cfg) {
}
function previewdata(key, idx, isinternal, hastime) {
- console.log("📦 previewdata() input:", {
- key,
- idx,
- isinternal,
- intdata: window.intdata,
- });
- console.log("key in previewdata", key);
+ // console.log("📦 previewdata() input:", {
+ // key,
+ // idx,
+ // isinternal,
+ // intdata: window.intdata,
+ // });
+ // console.log("key in previewdata", key);
if (!hasthreejs) {
$.when(
$.getScript("https://mcx.space/cloud/js/OrbitControls.js"),
@@ -257,21 +254,19 @@ function previewdata(key, idx, isinternal, hastime) {
).done(function () {
hasthreejs = true;
dopreview(key, idx, isinternal, hastime);
- console.log("into the previewdata function if");
});
} else {
dopreview(key, idx, isinternal, hastime);
- console.log("into the previewdata function else");
}
}
function dopreview(key, idx, isinternal, hastime) {
- console.log("🧪 dopreview input:", {
- key,
- idx,
- isinternal,
- intdata: intdata[idx],
- });
+ // console.log("🧪 dopreview input:", {
+ // key,
+ // idx,
+ // isinternal,
+ // intdata: intdata[idx],
+ // });
let ndim = 0;
if (hastime === undefined) hastime = [];
@@ -281,29 +276,41 @@ function dopreview(key, idx, isinternal, hastime) {
if (window.intdata && window.intdata[idx] && window.intdata[idx][2]) {
dataroot = window.intdata[idx][2];
} else {
- console.error("❌ Internal data not ready for index", idx);
+ // console.error("❌ Internal data not ready for index", idx);
return;
}
} else {
// dataroot = key;
- // console.log("into dopreview external data's dataroot", dataroot);
+ // new code start----
+ // if (typeof key === "object") {
+ // dataroot = key;
+ // console.log("dataroot======", dataroot);
+ // } else if (
+ // window.extdata &&
+ // window.extdata[idx] &&
+ // window.extdata[idx][2]
+ // ) {
+ // dataroot = window.extdata[idx][2];
+ // console.log("dataroot======>", dataroot);
+ // } else {
+ // console.error("External data not ready for index", idx);
+ // return;
+ // }
+ // new code end----
+ // original code start-----
if (window.extdata && window.extdata[idx] && window.extdata[idx][2]) {
if (typeof key === "object") {
dataroot = key;
- console.log("if key is object", typeof key);
} else {
dataroot = window.extdata[idx][2];
- console.log("type of key", typeof key);
}
-
// dataroot = key;
-
- console.log("into dopreview external data's dataroot", dataroot);
} else {
console.error("❌ External data not ready for index", idx);
return;
}
+ // original code end----
}
if (dataroot.hasOwnProperty("_ArraySize_")) {
@@ -319,9 +326,9 @@ function dopreview(key, idx, isinternal, hastime) {
dataroot = window.extdata[idx][2];
}
} else if (dataroot instanceof nj.NdArray) {
- console.log("dataroot before ndim", dataroot);
+ // console.log("dataroot before ndim", dataroot);
ndim = dataroot.shape.length;
- console.log("ndim", ndim);
+ // console.log("ndim", ndim);
}
if (ndim < 3 && ndim > 0) {
@@ -346,16 +353,16 @@ function dopreview(key, idx, isinternal, hastime) {
'Data preview
×'
);
if (dataroot instanceof nj.NdArray) {
- console.log("dataroot", dataroot);
+ // console.log("dataroot", dataroot);
if (dataroot.shape[0] > dataroot.shape[1])
dataroot = dataroot.transpose();
- console.log("is nj.NdArray:", dataroot instanceof nj.NdArray);
- console.log("dtype:", dataroot.dtype);
- console.log("shape:", dataroot.shape);
- console.log("size:", dataroot.size);
+ // console.log("is nj.NdArray:", dataroot instanceof nj.NdArray);
+ // console.log("dtype:", dataroot.dtype);
+ // console.log("shape:", dataroot.shape);
+ // console.log("size:", dataroot.size);
let plotdata = dataroot.tolist();
- console.log("plotdata", plotdata);
+ // console.log("plotdata", plotdata);
if (hastime.length == 0) {
if (plotdata[0] instanceof Array)
plotdata.unshift([...Array(plotdata[0].length).keys()]);
@@ -383,14 +390,12 @@ function dopreview(key, idx, isinternal, hastime) {
: hastime[i];
}
let u = new uPlot(opts, plotdata, document.getElementById("plotchart"));
- console.log("first u", u);
} else {
let u = new uPlot(
opts,
[[...Array(dataroot.length).keys()], dataroot],
document.getElementById("plotchart")
);
- console.log("second u", u);
}
// add spinner
// --- NEW LOGIC for 2D plot ---
@@ -542,7 +547,7 @@ function drawshape(shape, index) {
wireframe: true,
transparent: true,
});
- console.log("📌 Mesh Material:", material);
+ // console.log("📌 Mesh Material:", material);
obj = new THREE.Mesh(geometry, material);
obj.position.set(shape.Sphere.O[0], shape.Sphere.O[1], shape.Sphere.O[2]);
boundingbox.add(obj);
@@ -576,7 +581,7 @@ function drawshape(shape, index) {
wireframe: true,
transparent: false,
});
- console.log("📌 Mesh Material:", material);
+ // console.log("📌 Mesh Material:", material);
obj = new THREE.Mesh(geometry, material);
boundingbox.add(obj);
break;
@@ -591,11 +596,11 @@ function mulberry32(a) {
}
function drawsurf(node, tri) {
- console.log("🔷 Inside drawsurf()");
- console.log("📌 Received MeshNode:", node);
- console.log("📌 Received MeshSurf:", tri);
- console.log("📌 MeshNode Shape:", node.shape);
- console.log("📌 MeshSurf Shape:", tri.shape);
+ // console.log("🔷 Inside drawsurf()");
+ // console.log("📌 Received MeshNode:", node);
+ // console.log("📌 Received MeshSurf:", tri);
+ // console.log("📌 MeshNode Shape:", node.shape);
+ // console.log("📌 MeshSurf Shape:", tri.shape);
$("#mip-radio-button,#iso-radio-button,#interp-radio-button").prop(
"disabled",
true
@@ -621,14 +626,14 @@ function drawsurf(node, tri) {
side: THREE.DoubleSide,
wireframe: false,
});
- console.log("📌 Mesh Material:", material);
+ // console.log("📌 Mesh Material:", material);
lastvolume = new THREE.Mesh(geometry, material);
scene.add(lastvolume);
- console.log("🟢 Mesh Added to Scene:", lastvolume);
- console.log("📌 Mesh Position:", lastvolume.position);
- console.log("📌 Mesh Bounding Box:", lastvolume.geometry.boundingBox);
- console.log("📌 Mesh Bounding Sphere:", lastvolume.geometry.boundingSphere);
+ // console.log("🟢 Mesh Added to Scene:", lastvolume);
+ // console.log("📌 Mesh Position:", lastvolume.position);
+ // console.log("📌 Mesh Bounding Box:", lastvolume.geometry.boundingBox);
+ // console.log("📌 Mesh Bounding Sphere:", lastvolume.geometry.boundingSphere);
var geo = new THREE.WireframeGeometry(lastvolume.geometry);
var mat = new THREE.LineBasicMaterial({ color: 0x666666 });
@@ -656,11 +661,11 @@ function drawsurf(node, tri) {
boundingbox.add(lastvolume);
- console.log("👁 Camera Pos:", camera.position);
- console.log("👁 Controls Target:", controls.target);
- console.log("👁 Mesh Pos:", lastvolume.position);
- console.log("👁 Bounding Sphere:", geometry.boundingSphere);
- console.log("👁 Canvas size:", canvas.width(), canvas.height());
+ // console.log("👁 Camera Pos:", camera.position);
+ // console.log("👁 Controls Target:", controls.target);
+ // console.log("👁 Mesh Pos:", lastvolume.position);
+ // console.log("👁 Bounding Sphere:", geometry.boundingSphere);
+ // console.log("👁 Canvas size:", canvas.width(), canvas.height());
}
function resetscene(s) {
@@ -916,7 +921,6 @@ function initcanvas() {
}
if (renderer) {
- console.log("♻️ Resetting renderer and canvas...");
renderer.dispose();
$("#canvas").empty();
}
@@ -943,7 +947,7 @@ function initcanvas() {
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(canvas.width(), canvas.height());
canvas.append(renderer.domElement);
- console.log("✅