diff --git a/package.json b/package.json
index c5774d3..9ece9dc 100644
--- a/package.json
+++ b/package.json
@@ -20,19 +20,23 @@
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/jest": "^27.0.1",
+ "@types/numjs": "^0.16.8",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
- "@types/three": "^0.174.0",
+ "@types/three": "^0.176.0",
"ajv": "^8",
"ajv-keywords": "^5",
"axios": "^1.4.0",
"bda": "^1.0.0",
"bjd": "^0.3.2",
- "buffer": "^6.0.3",
+ "buffer": "6.0.3",
"dayjs": "^1.11.10",
"jquery": "^3.7.1",
"json-stringify-safe": "^5.0.1",
"jwt-decode": "^3.1.2",
+ "lzma": "^2.3.2",
+ "numjs": "^0.16.1",
+ "pako": "1.0.11",
"path-browserify": "^1.0.1",
"pako": "^2.1.0",
"query-string": "^8.1.0",
@@ -44,8 +48,10 @@
"react-scripts": "^5.0.1",
"sharp": "^0.33.5",
"stats-js": "^1.0.1",
+ "stats.js": "0.17.0",
+ "three": "0.145.0",
"typescript": "^5.1.6",
- "uplot": "^1.6.31",
+ "uplot": "1.6.17",
"web-vitals": "^2.1.0"
},
"devDependencies": {
diff --git a/src/components/PreviewModal.tsx b/src/components/PreviewModal.tsx
index 7ebc0a8..815acbb 100644
--- a/src/components/PreviewModal.tsx
+++ b/src/components/PreviewModal.tsx
@@ -1,117 +1,57 @@
+///
import React, { useEffect } from "react";
+// Tell TS that THREE is available globally
+declare const THREE: typeof import("three");
+
+declare global {
+ interface Window {
+ scene: any;
+ camera: any;
+ renderer: any;
+ previewdata: (...args: any[]) => void;
+ [key: string]: any;
+}
+}
+
const PreviewModal: React.FC<{
isOpen: boolean;
dataKey: any;
isInternal: boolean;
onClose: () => void;
}> = ({ isOpen, dataKey, isInternal, onClose }) => {
- useEffect(() => {
- if (isOpen) {
- console.log("๐ข Opening PreviewModal for:", dataKey);
-
- setTimeout(() => {
- // let canvas = document.querySelector("#canvas");
-
- // if (!canvas) {
- // console.error("โ Error: #canvas element not found in DOM!");
- // return;
- // }
-
- if (typeof (window as any).previewdata === "function") {
- console.log("Calling previewdata to handle Three.js init...");
- (window as any).previewdata(dataKey, 0, isInternal, false);
- } else {
- console.error("โ previewdata() is not defined!");
- }
- }, 100); // Small delay ensures modal is rendered first
+ useEffect(() => {
+ if (isOpen) {
+ console.log("๐ข Opening PreviewModal for:", dataKey);
+
+ // 1. Clear previous canvas
+ const canvasDiv = document.getElementById("canvas");
+ if (canvasDiv) {
+ while (canvasDiv.firstChild) {
+ canvasDiv.removeChild(canvasDiv.firstChild);
+ }
}
- }, [isOpen, dataKey]);
+
+ // 2. Clear global references
+ window.scene = undefined;
+ window.camera = undefined;
+ window.renderer = undefined;
+
+ // 3. Reinitialize preview
+ setTimeout(() => {
+ if (typeof window.previewdata === "function") {
+ console.log("Calling previewdata to handle Three.js init...");
+ window.previewdata(dataKey, 0, isInternal, false);
+ } else {
+ console.error("โ previewdata() is not defined!");
+ }
+ }, 100);
+ console.log("โณ Calling previewdata for", dataKey);
+ }
+}, [isOpen, dataKey]);
if (!isOpen) return null;
return (
- //
{
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 DatasetDetailPage: React.FC = () => {
const { dbName, docId } = useParams<{ dbName: string; docId: string }>();
const navigate = useNavigate();
@@ -76,124 +129,135 @@ const DatasetDetailPage: React.FC = () => {
error,
} = 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 [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);
-
- // Recursive function to find `_DataLink_`
- const extractDataLinks = (obj: any, path: string): ExternalDataLink[] => {
- const links: ExternalDataLink[] = [];
-
- const traverse = (node: any, currentPath: string) => {
- if (typeof node === "object" && node !== null) {
- for (const key in node) {
- if (key === "_DataLink_" && typeof node[key] === "string") {
- let correctedUrl = node[key].replace(/:\$.*$/, "");
-
- const sizeMatch = node[key].match(/size=(\d+)/);
- const size = sizeMatch
- ? `${(parseInt(sizeMatch[1], 10) / 1024 / 1024).toFixed(2)} MB`
- : "Unknown Size";
-
- const subMatch = currentPath.match(/sub-\d+/);
- const subPath = subMatch ? subMatch[0] : "Unknown Sub";
-
- links.push({
- name: `${
- currentPath.split("/").pop() || "ExternalData"
- } (${size}) [/${subPath}]`,
- size,
- path: currentPath, // keep full JSON path for file placement
- url: correctedUrl,
- index: links.length,
- });
- } else if (typeof node[key] === "object") {
- traverse(node[key], `${currentPath}/${key}`);
- }
- }
- }
- };
-
- traverse(obj, path);
- return links;
- };
-
- const extractInternalData = (obj: any, path = ""): InternalDataLink[] => {
- const internalLinks: InternalDataLink[] = [];
-
- if (obj && typeof obj === "object") {
- if (
- obj.hasOwnProperty("MeshNode") &&
- (obj.hasOwnProperty("MeshSurf") || obj.hasOwnProperty("MeshElem"))
- ) {
- if (
- obj.MeshNode.hasOwnProperty("_ArrayZipData_") &&
- typeof obj.MeshNode["_ArrayZipData_"] === "string"
- ) {
- internalLinks.push({
- name: `JMesh`,
- data: obj,
- index: internalLinks.length,
- arraySize: obj.MeshNode._ArraySize_,
- });
- }
- } else if (obj.hasOwnProperty("NIFTIData")) {
- if (
- obj.NIFTIData.hasOwnProperty("_ArrayZipData_") &&
- typeof obj.NIFTIData["_ArrayZipData_"] === "string"
- ) {
- internalLinks.push({
- name: `JNIfTI`,
- data: obj,
- index: internalLinks.length,
- arraySize: obj.NIFTIData._ArraySize_,
- });
- }
- } else if (
- obj.hasOwnProperty("_ArraySize_") &&
- !path.match("_EnumValue_$")
- ) {
- if (
- obj.hasOwnProperty("_ArrayZipData_") &&
- typeof obj["_ArrayZipData_"] === "string"
- ) {
- internalLinks.push({
- name: `JData`,
- data: obj,
- index: internalLinks.length,
- arraySize: obj._ArraySize_,
- });
- }
- } else {
- Object.keys(obj).forEach((key) => {
- if (typeof obj[key] === "object") {
- internalLinks.push(
- ...extractInternalData(
- obj[key],
- `${path}.${key.replace(/\./g, "\\.")}`
- )
- );
- }
- });
- }
- }
+ 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 [previewIsInternal, setPreviewIsInternal] = useState(false);
+ const [isExternalExpanded, setIsExternalExpanded] = useState(true);
+ const [expandedPaths, setExpandedPaths] = useState([]);
+ const [originalTextMap, setOriginalTextMap] = useState