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 + > + + {isPreviewable(node.link.url) && ( + + )} + + )} + + {/* internal preview action for folders */} + {internal && ( + e.stopPropagation()} + > + + + )} + + {/* 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 && ( 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 */} - {/* */} @@ -1067,48 +766,14 @@ const UpdatedDatasetDetailPage: React.FC = () => { > {/* Script to Download All Files ({downloadScript.length} Bytes) */} Script to Download All Files ({formatSize(downloadScriptSize)}) - {/* (links: {externalLinks.length}) */} {externalLinks.length > 0 && ` (links: ${externalLinks.length}, total: ${formatSize( totalFileSize )})`} - - {/* - - - */} -
{ }, 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")}]` : ""} + + + + )) + ) : ( + + 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} - - - )) - ) : ( - - 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 ( - - + + {isPreviewable && ( - {isPreviewable && ( - - )} - + )} - ); - }) - ) : ( - - 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("✅ appended:", renderer.domElement); + // console.log("✅ appended:", renderer.domElement); controls = new THREE.OrbitControls(camera, renderer.domElement); controls.minZoom = 0.5; @@ -1721,8 +1725,8 @@ function update() { } function previewdataurl(url, idx) { - console.log("🌍 Fetching external data from:", url); - console.log("🔍 Index:", idx); + // console.log("🌍 Fetching external data from:", url); + // console.log("🔍 Index:", idx); // if (!/\.(nii|nii\.gz|jdt|jdb|bmsh|jmsh|bnii|gz)$/i.test(url)) { if (!/file=.*\.(nii(\.gz)?|jdt|jdb|bmsh|jmsh|bnii)(?=(&|$))/i.test(url)) { @@ -1801,9 +1805,9 @@ function previewdataurl(url, idx) { let bjd; if (url.match(/\.nii\.gz/)) { - console.log("🔄 Processing NIfTI file..."); + // console.log("🔄 Processing NIfTI file..."); var origdata = pako.ungzip(arrayBuffer); - console.log("✅ Unzipped Data Length:", origdata.byteLength); + // console.log("✅ Unzipped Data Length:", origdata.byteLength); const header = new DataView(origdata.buffer); let headerlen = header.getUint32(0, true); let ndim = header.getUint16(40, true); @@ -1841,7 +1845,7 @@ function previewdataurl(url, idx) { NIFTIData: bjd.reshape(dims.reverse()).transpose(), }; } else { - console.log("🔄 Processing BJData..."); + // console.log("🔄 Processing BJData..."); bjd = bjdata.decode(buffer.Buffer.from(arrayBuffer)); // bjd = bjdata.decode(new Uint8Array(arrayBuffer)); let jd = new jdata(bjd[0], { base64: false }); @@ -1849,7 +1853,7 @@ function previewdataurl(url, idx) { } var plotdata = bjd; - console.log("plotdata", plotdata); + // console.log("plotdata", plotdata); if (linkpath.length > 1 && !linkpath[1].match(/^Mesh[NSEVT]/)) { let objpath = linkpath[1].split(/(?