diff --git a/src/FssTreeItem.ts b/src/FssTreeItem.ts index 7a54bfc..18a0d0b 100644 --- a/src/FssTreeItem.ts +++ b/src/FssTreeItem.ts @@ -12,6 +12,7 @@ import { INotebookTracker } from '@jupyterlab/notebook'; import { fileIcon, folderIcon } from '@jupyterlab/ui-components'; import { FssTreeItemContext } from './FssTreeItemContext'; +import { formatBytes } from './utils'; import { Logger } from './logger'; export class FssTreeItem { @@ -159,17 +160,38 @@ export class FssTreeItem { }); } - setMetadata(user_path: string, size: string) { + setMetadata(user_path: string, size: string, childrenCount?: number) { this.logger.debug('Setting item metadata', { path: user_path, - size + size, + childrenCount }); this.root.dataset.fss = user_path; this.root.dataset.fsize = size; - const sizeDisplay = `(${size.toLocaleString()})`; - this.sizeLbl.innerText = sizeDisplay; + if (childrenCount !== undefined) { + // For directories, show children count + const childrenDisplay = `(${childrenCount} item${childrenCount === 1 ? '' : 's'})`; + this.sizeLbl.innerText = childrenDisplay; + this.sizeLbl.style.display = 'block'; + } else if (this.isDir) { + // For directories without known count, hide the size label + this.sizeLbl.style.display = 'none'; + return; + } else { + // For files, show size + const sizeNumber = Number(size); + if (isNaN(sizeNumber)) { + this.logger.error('Invalid size', { size }); + return; + } + const formattedSize = formatBytes(sizeNumber); + const formattedSizeString = formattedSize.toString(); + + const sizeDisplay = `(${formattedSizeString})`; + this.sizeLbl.innerText = sizeDisplay; + } } setText(value: string) { @@ -191,6 +213,7 @@ export class FssTreeItem { if (symbol === 'file') { fileIcon.element({ container: this.dirSymbol }); this.isDir = false; + this.sizeLbl.style.display = 'block'; } } diff --git a/src/index.ts b/src/index.ts index 1cce824..1d49894 100644 --- a/src/index.ts +++ b/src/index.ts @@ -903,13 +903,21 @@ class FsspecWidget extends Widget { // Update the TreeView in the UI await this.updateFileBrowserView(nodeForPath); + // Update the children count for the expanded directory if (nodeForPath.id.toString() in this.elementHeap) { const uiElement = this.elementHeap[nodeForPath.id.toString()]; + const actualChildrenCount = Object.keys(nodeForPath.children).length; + uiElement.setMetadata( + nodeForPath.path, + nodeForPath.metadata.size, + actualChildrenCount + ); uiElement.expandItem(); this.logger.debug('Auto-expanded directory node', { path: source_path, - nodeId: nodeForPath.id + nodeId: nodeForPath.id, + childrenCount: actualChildrenCount }); } } @@ -991,10 +999,28 @@ class FsspecWidget extends Widget { true, this.notebookTracker ); - item.setMetadata( - (pathInfo as any).path, - (pathInfo as any).metadata.size - ); + const isDirectory = + Object.keys((pathInfo as any).children).length > 0 || + ('type' in (pathInfo as any).metadata && + (pathInfo as any).metadata.type === 'directory'); + + if (isDirectory) { + // Since directroy children are lazy-loaded: set children count to undefined + // The count will be updated once the directory is expanded + item.setMetadata( + (pathInfo as any).path, + (pathInfo as any).metadata.size, + undefined + ); + item.setType('dir'); + } else { + item.setMetadata( + (pathInfo as any).path, + (pathInfo as any).metadata.size + ); + item.setType('file'); + } + item.setText(pathSegment); item.treeItemClicked.connect(this.handleTreeItemClicked.bind(this)); item.getBytesRequested.connect( @@ -1008,18 +1034,6 @@ class FsspecWidget extends Widget { (pathInfo as any).id = item_id; this.elementHeap[item_id.toString()] = item; - // Set the item type (file or directory) - const isDirectory = - Object.keys((pathInfo as any).children).length > 0 || - ('type' in (pathInfo as any).metadata && - (pathInfo as any).metadata.type === 'directory'); - - if (isDirectory) { - item.setType('dir'); - } else { - item.setType('file'); - } - // Add children to build targets if needed if (Object.keys((pathInfo as any).children).length > 0) { buildTargets[(pathInfo as any).path] = [ diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..884d49c --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,28 @@ +export function formatBytes(n: number): string { + /** + * Examples: + * formatBytes(1) // '1 B' + * formatBytes(1234) // '1.21 kiB' + * formatBytes(12345678) // '11.77 MiB' + * formatBytes(1234567890) // '1.15 GiB' + * formatBytes(1234567890000) // '1.12 TiB' + * formatBytes(1234567890000000) // '1.10 PiB' + * + * For all values < 2^60, the output is always <= 10 characters. + * Note:Code adapted from dask.utils.format_bytes + */ + const units: [string, number][] = [ + ['Pi', Math.pow(2, 50)], + ['Ti', Math.pow(2, 40)], + ['Gi', Math.pow(2, 30)], + ['Mi', Math.pow(2, 20)], + ['ki', Math.pow(2, 10)] + ]; + + for (const [prefix, k] of units) { + if (n >= k * 0.9) { + return `${(n / k).toFixed(2)} ${prefix}B`; + } + } + return `${n} B`; +}