diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index c921c81..b2181f9 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -8,8 +8,6 @@ on: jobs: validate: runs-on: ubuntu-latest - env: - CI: false steps: - name: Check out the repository diff --git a/src/index.tsx b/src/index.tsx index 0bde327..ee78b23 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,24 +1,3 @@ -// import App from "./App"; -// import { ThemeProvider } from "@emotion/react"; -// import { CssBaseline } from "@mui/material"; -// import theme from "design/theme"; -// import ReactDOM from "react-dom/client"; -// import { Provider } from "react-redux"; -// import store from "redux/store"; - -// const root = ReactDOM.createRoot( -// document.getElementById("root") as HTMLElement -// ); - -// root.render( -// -// -// -// -// -// -// ); - import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; diff --git a/src/pages/DatasetDetailPage.tsx b/src/pages/DatasetDetailPage.tsx index 40c2cad..4318db8 100644 --- a/src/pages/DatasetDetailPage.tsx +++ b/src/pages/DatasetDetailPage.tsx @@ -60,7 +60,10 @@ const DatasetDetailPage: React.FC = () => { const [downloadScript, setDownloadScript] = useState(""); const [previewIsInternal, setPreviewIsInternal] = useState(false); const [isExternalExpanded, setIsExternalExpanded] = useState(true); - + const [expandedPaths, setExpandedPaths] = useState([]); + const [originalTextMap, setOriginalTextMap] = useState>(new Map()); + const [jsonViewerKey, setJsonViewerKey] = useState(0); + // Recursive function to find `_DataLink_` @@ -82,7 +85,7 @@ const DatasetDetailPage: React.FC = () => { const subPath = subMatch ? subMatch[0] : "Unknown Sub"; links.push({ - name: `NIFTIData (${size}) [/${subPath}]`, + name: `${path.split("/").pop() || "ExternalData"} (${size}) [/${subPath}]`, size, path: subPath, url: correctedUrl, @@ -194,10 +197,18 @@ const DatasetDetailPage: React.FC = () => { useEffect(() => { - if (searchTerm) { - highlightMatches(searchTerm); - } - }, [searchTerm, datasetDocument]); // ✅ Run search when dataset loads + 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]); const handleDownloadDataset = () => { if (!datasetDocument) return; @@ -230,22 +241,6 @@ const DatasetDetailPage: React.FC = () => { urlMatch: /\.(nii\.gz|jdt|jdb|bmsh|jmsh|bnii)$/i.test(dataOrUrl), }); - - // if (isInternal) { - // try { - // // ✅ Create a writable deep copy to avoid modifying read-only properties - // const writableData = JSON.parse(JSON.stringify(dataOrUrl)); - - // if (typeof (window as any).previewdata === "function") { - // console.log("✅ Calling previewdata() for internal data:", writableData); - // (window as any).previewdata(writableData, idx, false); // ✅ Pass writable copy - // } else { - // console.error("❌ previewdata() is not defined!"); - // } - // } catch (error) { - // console.error("❌ Error processing internal data:", error); - // } - // } if (isInternal) { try { // 🔐 Step 1: Ensure global intdata exists @@ -304,67 +299,82 @@ const DatasetDetailPage: React.FC = () => { }; const highlightMatches = (keyword: string) => { - if (!keyword.trim()) { - // ✅ Clear highlights when search is empty - document.querySelectorAll(".highlighted").forEach((el) => { - const element = el as HTMLElement; - if (element.dataset.originalText) { - element.innerText = element.dataset.originalText; - } - element.classList.remove("highlighted"); - }); - setMatches([]); - setHighlightedIndex(-1); - return; + // ✅ Step 1: Clean up all existing highlights + document.querySelectorAll(".highlighted").forEach((el) => { + const element = el as HTMLElement; + const text = element.textContent || ""; + element.innerHTML = text; // ❗ This clears out completely + element.classList.remove("highlighted"); + }); + + setOriginalTextMap(new Map()); // ✅ Also clear stored originals + + // ✅ Step 2: Early exit if search is too short + if (!keyword.trim() || keyword.length < 3) { + setMatches([]); + setHighlightedIndex(-1); + setExpandedPaths([]); + return; } - - const nodes: HTMLElement[] = []; - const elements = document.querySelectorAll(".react-json-view span"); // ✅ Select JSON values only - - elements.forEach((el) => { - const element = el as HTMLElement; - if (!element.textContent) return; // Skip empty elements - - const regex = new RegExp(`(${keyword})`, "gi"); - const originalText = element.dataset.originalText || element.textContent; - - if (originalText.toLowerCase().includes(keyword.toLowerCase())) { - if (!element.dataset.originalText) { - element.dataset.originalText = originalText; - } - - // ✅ Safe highlight without breaking structure - element.innerHTML = originalText.replace( - regex, - `$1` - ); - - nodes.push(element); - } + + // ✅ Step 3: Highlight matching keys/values + const matchedElements: HTMLElement[] = []; + const matchedPaths: Set = new Set(); + const newOriginalMap = new Map(); + + const spans = document.querySelectorAll( + ".react-json-view span.string-value, .react-json-view span.object-key" + ); + + const regex = new RegExp(`(${keyword})`, "gi"); + + spans.forEach((el) => { + const element = el as HTMLElement; + const text = element.textContent || ""; + + if (text.toLowerCase().includes(keyword.toLowerCase())) { + newOriginalMap.set(element, text); + + // Highlight match + const highlighted = text.replace( + regex, + `$1` + ); + element.innerHTML = highlighted; + matchedElements.push(element); + + // ✅ Collect path for expansion + const parent = element.closest(".variable-row"); + const path = parent?.getAttribute("data-path"); + if (path) matchedPaths.add(path); + } }); - - setMatches(nodes); // ✅ Store matches for "Find Next" + + // ✅ Step 4: Update React state + setOriginalTextMap(newOriginalMap); + setMatches(matchedElements); setHighlightedIndex(-1); - }; + setExpandedPaths(Array.from(matchedPaths)); + }; - - const findNext = () => { + const findNext = () => { if (matches.length === 0) return; - + setHighlightedIndex((prevIndex) => { - const nextIndex = (prevIndex + 1) % matches.length; - - matches.forEach((match) => { - match.style.backgroundColor = "lightyellow"; // Reset all highlights - }); - - matches[nextIndex].scrollIntoView({ behavior: "smooth", block: "center" }); - matches[nextIndex].style.backgroundColor = "orange"; // Highlight current match - - return nextIndex; + 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 ( @@ -520,7 +530,8 @@ const DatasetDetailPage: React.FC = () => { onChange={handleSearch} sx={{ width: "250px" }} /> - @@ -536,506 +547,221 @@ const DatasetDetailPage: React.FC = () => { enableClipboard={true} displayDataTypes={false} displayObjectSize={true} - collapsed={1} - style={{ fontSize: "14px", fontFamily: "monospace" }} + collapsed={searchTerm.length >= 3 ? false : 1} // 🔍 Expand during search + style={{ fontSize: "14px", fontFamily: "monospace" }} /> {/* ✅ Data panels (right panel) */} - - {/* Internal Data Section */} - {/* - - Internal Data ({internalLinks.length} objects) - - {internalLinks.length > 0 ? ( - internalLinks.map((link, index) => ( - - {link.name} [{link.arraySize?.join("x")}] - - - )) - ) : ( - No internal data found. - )} - */} - - - {/* ✅ Collapsible header */} - setIsInternalExpanded(!isInternalExpanded)} - > - - Internal Data ({internalLinks.length} objects) - - {isInternalExpanded ? : } - - - - {/* ✅ Scrollable area */} - - {internalLinks.length > 0 ? ( - internalLinks.map((link, index) => ( - - - {link.name} {link.arraySize ? `[${link.arraySize.join("x")}]` : ""} - - - - )) - ) : ( - No internal data found. - )} - - - - - {/* External Data Section */} - {/* - - External Data ({externalLinks.length} links) - - {externalLinks.length > 0 ? ( - externalLinks.map((link, index) => ( - - {link.name} - - - )) - ) : ( - No external links found. - )} - */} - - {/* ✅ Header with toggle */} - setIsExternalExpanded(!isExternalExpanded)} - > - - External Data ({externalLinks.length} links) - - {isExternalExpanded ? : } - - - - {/* ✅ Scrollable card container */} - - {externalLinks.length > 0 ? ( - externalLinks.map((link, index) => ( - - {link.name} - - - - - - )) - ) : ( - No external links found. - )} - - - - - - + + + {/* ✅ Collapsible header */} + setIsInternalExpanded(!isInternalExpanded)} + > + + Internal Data ({internalLinks.length} objects) + + {isInternalExpanded ? : } + - {/* + {/* ✅ Scrollable area */} + - {datasetDocument ? ( - - ) : ( - - No data available for this dataset. - - )} - - - {externalLinks.length > 0 && ( - + > + {internalLinks.length > 0 ? ( + internalLinks.map((link, index) => ( setIsExpanded(!isExpanded)} + key={index} sx={{ display: "flex", alignItems: "center", - cursor: "pointer", - marginBottom: 2, + justifyContent: "space-between", + padding: "6px 10px", + backgroundColor: "white", + borderRadius: "4px", + border: "1px solid #ddd", + mt: 1, + height: "34px", + minWidth: 0, + fontSize: "0.85rem", }} > - - External Data ({externalLinks.length} links) + + {link.name} {link.arraySize ? `[${link.arraySize.join("x")}]` : ""} - {isExpanded ? : } + - - + )) + ) : ( + No internal data found. + )} + + + + + {/* ✅ Header with toggle */} setIsExternalExpanded(!isExternalExpanded)} > - - - - Total Size:{" "} - {externalLinks - .reduce((acc, link) => { - const sizeMatch = link.size.match(/(\d+(\.\d+)?)/); - const sizeInMB = sizeMatch ? parseFloat(sizeMatch[1]) : 0; - return acc + sizeInMB; - }, 0) - .toFixed(2)}{" "} - MB - - + + 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 ( - {externalLinks.map((link, index) => ( - - - - {link.name} - - - Size: {link.size} - - - - - - - - - ))} + {link.name} + + + + {isPreviewable && ( + + )} + + ); + }) + ) : ( + No external links found. + )} - - + + - )} - - {internalLinks.length > 0 && ( - - setIsInternalExpanded(!isInternalExpanded)} - sx={{ - display: "flex", - alignItems: "center", - cursor: "pointer", - marginBottom: 2, - }} - > - - Internal Data ({internalLinks.length} objects) - - {isInternalExpanded ? : } - - - - - - {internalLinks.map((link, index) => ( - - - - {link.name} - - - Index: {link.index} - - - - - - - ))} - - - - -)} */} - - - - {/* ✅ ADD FLASHCARDS COMPONENT HERE ✅ */} + + {/* ✅ ADD FLASHCARDS COMPONENT HERE ✅ */} -
+
- - {/* Preview Modal Component - Add Here */} - - {/*
*/} - - ); -}; + + {/* Preview Modal Component - Add Here */} + + + )}; export default DatasetDetailPage; diff --git a/yarn.lock b/yarn.lock index 1ed1161..d3d3d4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4812,9 +4812,9 @@ ejs@^3.1.6: jake "^10.8.5" electron-to-chromium@^1.5.73: - version "1.5.126" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.126.tgz#51c3be94c25710f2f49ffddbab5aca8983e11e2f" - integrity sha512-AtH1uLcTC72LA4vfYcEJJkrMk/MY/X0ub8Hv7QGAePW2JkeUFHEL/QfS4J77R6M87Sss8O0OcqReSaN1bpyA+Q== + version "1.5.127" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.127.tgz#350a16aa09fb7f070ad118fade31260a5c173733" + integrity sha512-Ke5OggqOtEqzCzcUyV+9jgO6L6sv1gQVKGtSExXHjD/FK0p4qzPZbrDsrCdy0DptcQprD0V80RCBYSWLMhTTgQ== emittery@^0.10.2: version "0.10.2"