@@ -40,6 +40,31 @@ interface InternalDataLink {
4040 arraySize ?: number [ ] ;
4141}
4242
43+ const transformJsonForDisplay = ( obj : any ) : any => {
44+ if ( typeof obj !== "object" || obj === null ) return obj ;
45+
46+ const transformed : any = Array . isArray ( obj ) ? [ ] : { } ;
47+
48+ for ( const key in obj ) {
49+ if ( ! Object . prototype . hasOwnProperty . call ( obj , key ) ) continue ;
50+
51+ const value = obj [ key ] ;
52+
53+ // Match README, CHANGES, or file extensions
54+ const isLongTextKey = / ^ ( R E A D M E | C H A N G E S ) $ | \. m d $ | \. t x t $ | \. m $ / i. test ( key ) ;
55+
56+ if ( typeof value === "string" && isLongTextKey ) {
57+ transformed [ key ] = `<code class="puretext">${ value } </code>` ;
58+ } else if ( typeof value === "object" ) {
59+ transformed [ key ] = transformJsonForDisplay ( value ) ;
60+ } else {
61+ transformed [ key ] = value ;
62+ }
63+ }
64+
65+ return transformed ;
66+ } ;
67+
4368const DatasetDetailPage : React . FC = ( ) => {
4469 const { dbName, docId } = useParams < { dbName : string ; docId : string } > ( ) ;
4570 const navigate = useNavigate ( ) ;
@@ -63,41 +88,43 @@ const DatasetDetailPage: React.FC = () => {
6388 const [ expandedPaths , setExpandedPaths ] = useState < string [ ] > ( [ ] ) ;
6489 const [ originalTextMap , setOriginalTextMap ] = useState < Map < HTMLElement , string > > ( new Map ( ) ) ;
6590 const [ jsonViewerKey , setJsonViewerKey ] = useState ( 0 ) ;
66-
91+ const [ jsonSize , setJsonSize ] = useState < number > ( 0 ) ;
92+ const [ transformedDataset , setTransformedDataset ] = useState < any > ( null ) ;
6793
6894
6995 // Recursive function to find `_DataLink_`
7096 const extractDataLinks = ( obj : any , path : string ) : ExternalDataLink [ ] => {
7197 const links : ExternalDataLink [ ] = [ ] ;
72-
73- if ( typeof obj === "object" && obj !== null ) {
74- for ( const key in obj ) {
75- if ( obj . hasOwnProperty ( key ) ) {
76- if ( key === "_DataLink_" && typeof obj [ key ] === "string" ) {
77- let correctedUrl = obj [ key ] . replace ( / : \$ .* $ / , "" ) ;
78-
79- const sizeMatch = obj [ key ] . match ( / s i z e = ( \d + ) / ) ;
98+
99+ const traverse = ( node : any , currentPath : string ) => {
100+ if ( typeof node === "object" && node !== null ) {
101+ for ( const key in node ) {
102+ if ( key === "_DataLink_" && typeof node [ key ] === "string" ) {
103+ let correctedUrl = node [ key ] . replace ( / : \$ .* $ / , "" ) ;
104+
105+ const sizeMatch = node [ key ] . match ( / s i z e = ( \d + ) / ) ;
80106 const size = sizeMatch
81107 ? `${ ( parseInt ( sizeMatch [ 1 ] , 10 ) / 1024 / 1024 ) . toFixed ( 2 ) } MB`
82108 : "Unknown Size" ;
83-
84- const subMatch = path . match ( / s u b - \d + / ) ;
109+
110+ const subMatch = currentPath . match ( / s u b - \d + / ) ;
85111 const subPath = subMatch ? subMatch [ 0 ] : "Unknown Sub" ;
86-
112+
87113 links . push ( {
88- name : `${ path . split ( "/" ) . pop ( ) || "ExternalData" } (${ size } ) [/${ subPath } ]` ,
114+ name : `${ currentPath . split ( "/" ) . pop ( ) || "ExternalData" } (${ size } ) [/${ subPath } ]` ,
89115 size,
90- path : subPath ,
116+ path : currentPath , // keep full JSON path for file placement
91117 url : correctedUrl ,
92118 index : links . length ,
93119 } ) ;
94- } else if ( typeof obj [ key ] === "object" ) {
95- links . push ( ... extractDataLinks ( obj [ key ] , `${ path } /${ key } ` ) ) ;
120+ } else if ( typeof node [ key ] === "object" ) {
121+ traverse ( node [ key ] , `${ currentPath } /${ key } ` ) ;
96122 }
97123 }
98124 }
99- }
100-
125+ } ;
126+
127+ traverse ( obj , path ) ;
101128 return links ;
102129 } ;
103130
@@ -185,9 +212,24 @@ const DatasetDetailPage: React.FC = () => {
185212
186213 setExternalLinks ( links ) ;
187214 setInternalLinks ( internalData ) ;
215+ const transformed = transformJsonForDisplay ( datasetDocument ) ;
216+ setTransformedDataset ( transformed ) ;
217+
218+ const blob = new Blob ( [ JSON . stringify ( datasetDocument , null , 2 ) ] , { type : "application/json" } ) ;
219+ setJsonSize ( blob . size ) ;
220+
221+ // // ✅ Construct download script dynamically
222+ let script = `curl -L --create-dirs "https://neurojson.io:7777/${ dbName } /${ docId } " -o "${ docId } .json"\n` ;
188223
189- // ✅ Construct download script dynamically
190- const script = `curl -L --create-dirs "https://neurojson.io:7777/${ dbName } /${ docId } " -o "${ docId } .json"` ;
224+
225+ externalLinks . forEach ( ( link ) => {
226+ const url = link . url ;
227+ const match = url . match ( / f i l e = ( [ ^ & ] + ) / ) ;
228+ const filename = match ? decodeURIComponent ( match [ 1 ] ) : `file-${ link . index } ` ;
229+ const outputPath = `$HOME/.neurojson/io/${ dbName } /${ docId } /${ filename } ` ;
230+
231+ script += `curl -L --create-dirs "${ url } " -o "${ outputPath } "\n` ;
232+ } ) ;
191233 setDownloadScript ( script ) ;
192234 }
193235 } , [ datasetDocument ] ) ;
@@ -208,7 +250,20 @@ const DatasetDetailPage: React.FC = () => {
208250 element . classList . remove ( "highlighted" ) ;
209251 } ) ;
210252 } ;
211- } , [ searchTerm , datasetDocument ] ) ;
253+ } , [ searchTerm , datasetDocument ] ) ;
254+
255+ useEffect ( ( ) => {
256+ if ( ! transformedDataset ) return ;
257+
258+ const spans = document . querySelectorAll ( ".string-value" ) ;
259+
260+ spans . forEach ( ( el ) => {
261+ if ( el . textContent ?. includes ( "<code class=\"puretext\">" ) ) {
262+ // Inject as HTML so it renders code block correctly
263+ el . innerHTML = el . textContent ?? "" ;
264+ }
265+ } ) ;
266+ } , [ transformedDataset ] ) ;
212267
213268 const handleDownloadDataset = ( ) => {
214269 if ( ! datasetDocument ) return ;
@@ -402,6 +457,32 @@ const DatasetDetailPage: React.FC = () => {
402457 }
403458
404459 return (
460+ < >
461+ { /* 🔧 Inline CSS for string formatting */ }
462+ < style >
463+ { `
464+ code.puretext {
465+ white-space: pre-wrap;
466+ display: -webkit-box;
467+ -webkit-box-orient: vertical;
468+ -webkit-line-clamp: 4;
469+ overflow: hidden;
470+ text-overflow: ellipsis;
471+ font-family: monospace;
472+ color: #d14;
473+ font-size: 14px;
474+ background-color: transparent;
475+ cursor: pointer;
476+ transition: all 0.2s ease;
477+ }
478+
479+ code.puretext:hover, code.puretext:focus {
480+ -webkit-line-clamp: unset;
481+ overflow: visible;
482+ background-color: #f0f0f0;
483+ }` }
484+
485+ </ style >
405486 < Box sx = { { padding : 4 } } >
406487 < Button
407488 variant = "contained"
@@ -413,12 +494,12 @@ const DatasetDetailPage: React.FC = () => {
413494
414495 < Box
415496 sx = { {
416- position : "sticky" , // ✅ Keeps title & search bar fixed
417- top : 0 , // ✅ Sticks to the top
418- backgroundColor : "white" , // ✅ Ensures smooth UI
419- zIndex : 10 , // ✅ Keeps it above scrollable content
420- paddingBottom : 2 , // ✅ Adds space for clarity
421- borderBottom : `1px solid ${ Colors . lightGray } ` , // ✅ Adds subtle separator
497+ position : "sticky" ,
498+ top : 0 ,
499+ backgroundColor : "white" ,
500+ zIndex : 10 ,
501+ paddingBottom : 2 ,
502+ borderBottom : `1px solid ${ Colors . lightGray } ` ,
422503 } } >
423504
424505 { /* ✅ Dataset Title (From dataset_description.json) */ }
@@ -462,7 +543,7 @@ const DatasetDetailPage: React.FC = () => {
462543
463544 { /* Database Name (Clickable) */ }
464545 < Button
465- onClick = { ( ) => navigate ( `/RoutesEnum.DATABASES /${ dbName } ` ) }
546+ onClick = { ( ) => navigate ( `/databases /${ dbName } ` ) }
466547 sx = { {
467548 textTransform : "none" ,
468549 fontSize : "1.2rem" ,
@@ -505,7 +586,8 @@ const DatasetDetailPage: React.FC = () => {
505586 "&:hover" : { backgroundColor : "#ff9100" } ,
506587 } }
507588 >
508- Download Dataset (1 Mb)
589+ { /* Download Dataset (1 Mb) */ }
590+ Download Dataset ({ ( jsonSize / 1024 ) . toFixed ( 0 ) } MB)
509591 </ Button >
510592
511593 < Button
@@ -518,7 +600,7 @@ const DatasetDetailPage: React.FC = () => {
518600 "&:hover" : { backgroundColor : "#ff9100" } ,
519601 } }
520602 >
521- Script to Download All Files (138 Bytes) (links: 0 )
603+ Script to Download All Files ({ downloadScript . length } Bytes) (links: { externalLinks . length } )
522604 </ Button >
523605
524606 < Box display = "flex" alignItems = "center" gap = { 1 } sx = { { ml : "auto" } } >
@@ -542,7 +624,7 @@ const DatasetDetailPage: React.FC = () => {
542624 { /* ✅ JSON Viewer (left panel) */ }
543625 < Box sx = { { flex : 3 , backgroundColor : "#f5f5f5" , padding : 2 , borderRadius : "8px" , overflowX : "auto" } } >
544626 < ReactJson
545- src = { datasetDocument }
627+ src = { transformedDataset || datasetDocument }
546628 name = { false }
547629 enableClipboard = { true }
548630 displayDataTypes = { false }
@@ -761,7 +843,8 @@ const DatasetDetailPage: React.FC = () => {
761843 isInternal = { previewIsInternal }
762844 onClose = { handleClosePreview }
763845 />
764- </ Box >
846+ </ Box >
847+ </ >
765848 ) } ;
766849
767850export default DatasetDetailPage ;
0 commit comments