-
+
-{fileTree.length > 0 && (
-
)}
@@ -538,53 +471,13 @@ function getFileIcon(filename: string): string {
- {fileTree.map((node) => {
- // Recursive rendering function defined inline
- const renderNode = (n: TreeNode): any => {
- if (n.type === 'folder') {
- return (
-
-
- {n.name}
- {n.children?.map((child) => renderNode(child))}
-
- );
- } else {
- return (
-
-
- {n.name}
- {n.size && (
-
- {n.size}
-
-
-
- )}
-
- );
- }
- };
- return renderNode(node);
- })}
+
+
+
+
+
Loading file tree...
+
@@ -706,6 +599,9 @@ function getFileIcon(filename: string): string {
import { dicomLoader } from '@niivue/dicom-loader';
import { downloadZip } from 'client-zip';
+ // File tree builder (lazy-loading)
+ import { FileTreeManager } from '../../../lib/fileTreeBuilder';
+
// Import highlight.js core and individual languages
import hljs from 'highlight.js/lib/core';
import python from 'highlight.js/lib/languages/python';
@@ -840,7 +736,7 @@ function getFileIcon(filename: string): string {
};
// Helper function to find companion file for MHD/RAW pairs
- const findCompanionFile = (filename: string, fileTreeData: any[]): string | null => {
+ const findCompanionFile = (filename: string): string | null => {
const baseNameMatch = filename.match(/^(.+)\.(mhd|raw)$/i);
if (!baseNameMatch) return null;
@@ -849,25 +745,20 @@ function getFileIcon(filename: string): string {
const companionExt = currentExt === 'mhd' ? 'raw' : 'mhd';
const companionFilename = `${baseName}.${companionExt}`;
- // Search recursively in the file tree
- const searchTree = (nodes: any[]): string | null => {
- for (const node of nodes) {
- if (node.type === 'file' && node.name.toLowerCase() === companionFilename.toLowerCase()) {
- return node.url;
- }
- if (node.children) {
- const found = searchTree(node.children);
- if (found) return found;
- }
+ // Search in flat codeFiles array
+ const codeFiles = (window as any).codeFilesData || [];
+ for (const file of codeFiles) {
+ const fileTitle = file.title || '';
+ const fileName = fileTitle.split('/').pop() || '';
+ if (fileName.toLowerCase() === companionFilename.toLowerCase()) {
+ return file.url;
}
- return null;
- };
-
- return searchTree(fileTreeData);
+ }
+ return null;
};
// Helper function to find companion file for HDR/IMG pairs
- const findHdrImgCompanion = (filename: string, fileTreeData: any[]): string | null => {
+ const findHdrImgCompanion = (filename: string): string | null => {
const baseNameMatch = filename.match(/^(.+)\.(hdr|img)$/i);
if (!baseNameMatch) return null;
@@ -876,21 +767,16 @@ function getFileIcon(filename: string): string {
const companionExt = currentExt === 'hdr' ? 'img' : 'hdr';
const companionFilename = `${baseName}.${companionExt}`;
- // Search recursively in the file tree
- const searchTree = (nodes: any[]): string | null => {
- for (const node of nodes) {
- if (node.type === 'file' && node.name.toLowerCase() === companionFilename.toLowerCase()) {
- return node.url;
- }
- if (node.children) {
- const found = searchTree(node.children);
- if (found) return found;
- }
+ // Search in flat codeFiles array
+ const codeFiles = (window as any).codeFilesData || [];
+ for (const file of codeFiles) {
+ const fileTitle = file.title || '';
+ const fileName = fileTitle.split('/').pop() || '';
+ if (fileName.toLowerCase() === companionFilename.toLowerCase()) {
+ return file.url;
}
- return null;
- };
-
- return searchTree(fileTreeData);
+ }
+ return null;
};
// Helper function to combine MHD and RAW files into MHA format
@@ -1006,7 +892,7 @@ function getFileIcon(filename: string): string {
await nv.loadDicoms([{ url, name: filename }]);
} else if (ext.endsWith('.mhd') || ext.endsWith('.raw')) {
// Check for MHD/RAW companion files and combine them
- const companionUrl = findCompanionFile(filename, (window as any).fileTreeData);
+ const companionUrl = findCompanionFile(filename);
if (companionUrl) {
// Determine which is MHD and which is RAW
const isMhd = ext.endsWith('.mhd');
@@ -1032,7 +918,7 @@ function getFileIcon(filename: string): string {
}
} else if (ext.endsWith('.hdr') || ext.endsWith('.img')) {
// Check for HDR/IMG companion files
- const companionUrl = findHdrImgCompanion(filename, (window as any).fileTreeData);
+ const companionUrl = findHdrImgCompanion(filename);
if (companionUrl) {
// Determine which is HDR and which is IMG
const isHdr = ext.endsWith('.hdr');
@@ -1201,6 +1087,15 @@ function getFileIcon(filename: string): string {
});
}
+ // File tree manager instance (lazy-loading tree builder)
+ const fileTreeManager = new FileTreeManager();
+
+ // Build file tree dynamically (only top-level items, children loaded on expand)
+ const buildFileTree = () => {
+ const codeFiles = (window as any).codeFilesData;
+ fileTreeManager.initialize(codeFiles);
+ };
+
// Handle download all files as ZIP
const downloadAllButton = document.getElementById('download-all-button') as HTMLButtonElement;
if (downloadAllButton) {
@@ -1295,14 +1190,30 @@ function getFileIcon(filename: string): string {
}
if (tabName === 'files') {
- // Load default file if available
- const defaultFileEl = document.querySelector('.selected-file') as HTMLElement;
- if (defaultFileEl && currentViewerType === 'none') {
- const url = defaultFileEl.dataset.url;
- const path = defaultFileEl.dataset.path;
- if (url && path) {
- const filename = path.split('/').pop() || 'file';
- await loadFile(url, filename);
+ // Build file tree on first visit
+ buildFileTree();
+
+ // Load first available file if nothing loaded yet
+ // Priority order matches original findDefaultFile: volume -> mesh -> image -> any
+ if (currentViewerType === 'none') {
+ const codeFiles = (window as any).codeFilesData || [];
+ if (codeFiles.length > 0) {
+ const volumeExts = (window as any).volumeExtensions || [];
+ const meshExts = (window as any).meshExtensions || [];
+ const imageExts = (window as any).imageExtensions || [];
+
+ const findByExtensions = (exts: string[]) =>
+ codeFiles.find((f: any) => exts.some((ext: string) => f.title?.toLowerCase().endsWith(ext)));
+
+ let defaultFile = findByExtensions(volumeExts);
+ if (!defaultFile) defaultFile = findByExtensions(meshExts);
+ if (!defaultFile) defaultFile = findByExtensions(imageExts);
+ if (!defaultFile) defaultFile = codeFiles[0];
+
+ if (defaultFile?.url && defaultFile?.title) {
+ const filename = defaultFile.title.split('/').pop() || 'file';
+ await loadFile(defaultFile.url, filename);
+ }
}
}
}
@@ -1721,6 +1632,31 @@ function getFileIcon(filename: string): string {
--indent-guide-width: 1px;
}
+ /* File tree loading placeholder */
+ .file-tree-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: var(--wa-space-2xl);
+ color: var(--wa-color-text-quiet);
+ }
+
+ .file-tree-loading p {
+ margin-top: var(--wa-space-m);
+ font-size: var(--wa-font-size-m);
+ }
+
+ .loading-spinner {
+ font-size: 2rem;
+ animation: spin 1s linear infinite;
+ }
+
+ @keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+
.file-size {
font-size: var(--wa-font-size-xs);
color: var(--wa-color-text-quiet);