Skip to content

Commit aa61ba8

Browse files
authored
Upgrade hightable (#279)
* update dev dependencies * upgrade hightable * refactor * refactor + use onPage * show a warning message instead of an error when the cell has not loaded yet * fix logic in groups fetching * fix the content of the cell panel * don't open the cell panel until the cell has loaded
1 parent c4b6f37 commit aa61ba8

File tree

17 files changed

+239
-363
lines changed

17 files changed

+239
-363
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"watch:url": "NODE_ENV=development nodemon bin/cli.js https://hyperparam.blob.core.windows.net/hyperparam/starcoderdata-js-00000-of-00065.parquet"
5656
},
5757
"dependencies": {
58-
"hightable": "0.17.2",
58+
"hightable": "0.18.1",
5959
"hyparquet": "1.17.1",
6060
"hyparquet-compressors": "1.1.1",
6161
"icebird": "0.3.0",
@@ -67,8 +67,8 @@
6767
"@storybook/react-vite": "9.0.18",
6868
"@testing-library/react": "16.3.0",
6969
"@types/node": "24.1.0",
70-
"@types/react": "19.1.8",
71-
"@types/react-dom": "19.1.6",
70+
"@types/react": "19.1.9",
71+
"@types/react-dom": "19.1.7",
7272
"@vitejs/plugin-react": "4.7.0",
7373
"@vitest/coverage-v8": "3.2.4",
7474
"eslint": "9.32.0",

src/components/AvroView/AvroView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import styles from '../Json/Json.module.css'
88

99
interface ViewerProps {
1010
source: FileSource
11-
setError: (error: Error | undefined) => void
11+
setError: (error: unknown) => void
1212
}
1313

1414
/**
@@ -43,7 +43,7 @@ export default function AvroView({ source, setError }: ViewerProps) {
4343
setContent({ fileSize })
4444
setJson(json)
4545
} catch (error) {
46-
setError(error as Error)
46+
setError(error)
4747
} finally {
4848
setIsLoading(false)
4949
}

src/components/Cell/Cell.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ interface CellProps {
1515
col: number
1616
}
1717

18+
const UNLOADED_CELL_PLACEHOLDER = '<the content has not been fetched yet>'
19+
1820
/**
1921
* Cell viewer displays a single cell from a table.
2022
*/
@@ -39,21 +41,14 @@ export default function CellView({ source, row, col }: CellProps) {
3941
const metadata = await parquetMetadataAsync(asyncBuffer)
4042
setProgress(0.75)
4143
const df = parquetDataFrame(from, metadata)
42-
const asyncRows = df.rows({ start: row, end: row + 1 })
43-
if (asyncRows.length > 1 || !(0 in asyncRows)) {
44-
throw new Error(`Expected 1 row, got ${asyncRows.length}`)
45-
}
46-
const asyncRow = asyncRows[0]
47-
// Await cell data
44+
4845
const columnName = df.header[col]
4946
if (columnName === undefined) {
5047
throw new Error(`Column name missing at index col=${col}`)
5148
}
52-
const asyncCell = asyncRow.cells[columnName]
53-
if (asyncCell === undefined) {
54-
throw new Error(`Cell missing at column ${columnName}`)
55-
}
56-
const text = await asyncCell.then(stringify)
49+
await df.fetch({ rowStart: row, rowEnd: row + 1, columns: [columnName] })
50+
const cell = df.getCell({ row, column: columnName })
51+
const text = cell === undefined ? UNLOADED_CELL_PLACEHOLDER : stringify(cell.value)
5752
setText(text)
5853
setError(undefined)
5954
} catch (error) {

src/components/CellPanel/CellPanel.tsx

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,78 @@
1-
import { DataFrame, stringify } from 'hightable'
2-
import { ReactNode, useEffect, useState } from 'react'
1+
import type { DataFrame, ResolvedValue } from 'hightable'
2+
import { stringify } from 'hightable'
3+
import { ReactNode, useCallback, useEffect, useState } from 'react'
34
import { useConfig } from '../../hooks/useConfig.js'
45
import { cn } from '../../lib/utils.js'
56
import ContentWrapper from '../ContentWrapper/ContentWrapper.js'
67
import Json from '../Json/Json.js'
8+
import jsonStyles from '../Json/Json.module.css'
79
import SlideCloseButton from '../SlideCloseButton/SlideCloseButton.js'
810
import styles from '../TextView/TextView.module.css'
9-
import jsonStyles from '../Json/Json.module.css'
1011

1112
interface ViewerProps {
1213
df: DataFrame
1314
row: number
1415
col: number
1516
setProgress: (progress: number) => void
16-
setError: (error: Error) => void
17+
setError: (error: unknown) => void
1718
onClose: () => void
1819
}
1920

21+
const UNLOADED_CELL_PLACEHOLDER = '<the content has not been fetched yet>'
22+
2023
/**
2124
* Cell viewer displays a single cell from a table.
2225
*/
2326
export default function CellPanel({ df, row, col, setProgress, setError, onClose }: ViewerProps) {
2427
const [content, setContent] = useState<ReactNode>()
2528
const { customClass } = useConfig()
2629

30+
const fillContent = useCallback((cell: ResolvedValue<unknown> | undefined) => {
31+
let content: ReactNode
32+
if (cell === undefined) {
33+
content =
34+
<code className={cn(jsonStyles.textView, customClass?.textView)}>
35+
{UNLOADED_CELL_PLACEHOLDER}
36+
</code>
37+
} else {
38+
const { value } = cell
39+
if (value instanceof Object && !(value instanceof Date)) {
40+
content =
41+
<code className={cn(jsonStyles.jsonView, customClass?.jsonView)}>
42+
<Json json={value} />
43+
</code>
44+
} else {
45+
content =
46+
<code className={cn(styles.textView, customClass?.textView)}>
47+
{stringify(value)}
48+
</code>
49+
}
50+
}
51+
setContent(content)
52+
setError(undefined)
53+
}, [customClass?.textView, customClass?.jsonView, setError])
54+
2755
// Load cell data
2856
useEffect(() => {
2957
async function loadCellData() {
3058
try {
3159
setProgress(0.5)
32-
const asyncRows = df.rows({ start: row, end: row + 1 })
33-
if (asyncRows.length > 1 || !(0 in asyncRows)) {
34-
throw new Error(`Expected 1 row, got ${asyncRows.length}`)
35-
}
36-
const asyncRow = asyncRows[0]
37-
// Await cell data
60+
3861
const columnName = df.header[col]
3962
if (columnName === undefined) {
4063
throw new Error(`Column name missing at index col=${col}`)
4164
}
42-
const asyncCell = asyncRow.cells[columnName]
43-
if (asyncCell === undefined) {
44-
throw new Error(`Cell missing at column ${columnName}`)
65+
let cell = df.getCell({ row, column: columnName })
66+
if (cell === undefined) {
67+
fillContent(undefined)
68+
return
4569
}
46-
const value: unknown = await asyncCell
47-
if (value instanceof Object && !(value instanceof Date)) {
48-
setContent(
49-
<code className={cn(jsonStyles.jsonView, customClass?.jsonView)}>
50-
<Json json={value} />
51-
</code>
52-
)
53-
} else {
54-
setContent(
55-
<code className={cn(styles.textView, customClass?.textView)}>
56-
{stringify(value)}
57-
</code>
58-
)
70+
await df.fetch({ rowStart: row, rowEnd: row + 1, columns: [columnName] })
71+
cell = df.getCell({ row, column: columnName })
72+
if (cell === undefined) {
73+
throw new Error(`Cell at row=${row}, column=${columnName} is undefined`)
5974
}
75+
fillContent(cell)
6076
} catch (error) {
6177
setError(error as Error)
6278
} finally {
@@ -65,7 +81,7 @@ export default function CellPanel({ df, row, col, setProgress, setError, onClose
6581
}
6682

6783
void loadCellData()
68-
}, [df, col, row, setProgress, setError, customClass])
84+
}, [df, col, row, setProgress, setError, fillContent])
6985

7086
const headers = <>
7187
<SlideCloseButton onClick={onClose} />

src/components/File/File.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { useState } from 'react'
1+
import { useCallback, useState } from 'react'
22
import type { FileSource } from '../../lib/sources/types.js'
3+
import { toError } from '../../lib/utils.js'
34
import Breadcrumb from '../Breadcrumb/Breadcrumb.js'
45
import Layout from '../Layout/Layout.js'
56
import Viewer from '../Viewer/Viewer.js'
@@ -13,10 +14,14 @@ interface FileProps {
1314
*/
1415
export default function File({ source }: FileProps) {
1516
const [progress, setProgress] = useState<number>()
16-
const [error, setError] = useState<Error>()
17+
const [error, setError] = useState<Error | undefined>()
18+
19+
const setErrorWrapper = useCallback((error: unknown) => {
20+
setError(toError(error))
21+
}, [setError])
1722

1823
return <Layout progress={progress} error={error} title={source.fileName}>
1924
<Breadcrumb source={source} />
20-
<Viewer source={source} setProgress={setProgress} setError={setError} />
25+
<Viewer source={source} setProgress={setProgress} setError={setErrorWrapper} />
2126
</Layout>
2227
}

src/components/ImageView/ImageView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import styles from './ImageView.module.css'
77

88
interface ViewerProps {
99
source: FileSource
10-
setError: (error: Error | undefined) => void
10+
setError: (error: unknown) => void
1111
}
1212

1313
interface Content {
@@ -45,7 +45,7 @@ export default function ImageView({ source, setError }: ViewerProps) {
4545
setError(undefined)
4646
} catch (error) {
4747
setContent(undefined)
48-
setError(error as Error)
48+
setError(error)
4949
} finally {
5050
setIsLoading(false)
5151
}

src/components/JsonView/JsonView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import styles from '../Json/Json.module.css'
88

99
interface ViewerProps {
1010
source: FileSource
11-
setError: (error: Error | undefined) => void
11+
setError: (error: unknown) => void
1212
}
1313

1414
const largeFileSize = 8_000_000 // 8 mb
@@ -48,7 +48,7 @@ export default function JsonView({ source, setError }: ViewerProps) {
4848
setJson(JSON.parse(text))
4949
} catch (error) {
5050
// TODO: show plain text in error case
51-
setError(error as Error)
51+
setError(error)
5252
} finally {
5353
setIsLoading(false)
5454
}

src/components/MarkdownView/MarkdownView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import styles from './MarkdownView.module.css'
88

99
interface ViewerProps {
1010
source: FileSource
11-
setError: (error: Error | undefined) => void
11+
setError: (error: unknown) => void
1212
}
1313

1414
/**
@@ -37,7 +37,7 @@ export default function MarkdownView({ source, setError }: ViewerProps) {
3737
setError(undefined)
3838
setContent({ text, fileSize })
3939
} catch (error) {
40-
setError(error as Error)
40+
setError(error)
4141
setContent(undefined)
4242
} finally {
4343
setIsLoading(false)

src/components/ParquetView/ParquetView.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import HighTable, { DataFrame, rowCache } from 'hightable'
1+
import HighTable, { DataFrame } from 'hightable'
22
import 'hightable/src/HighTable.css'
33
import { asyncBufferFromUrl, parquetMetadataAsync } from 'hyparquet'
44
import React, { useCallback, useEffect, useState } from 'react'
@@ -15,7 +15,7 @@ import styles from './ParquetView.module.css'
1515
interface ViewerProps {
1616
source: FileSource
1717
setProgress: (progress: number | undefined) => void
18-
setError: (error: Error | undefined) => void
18+
setError: (error: unknown) => void
1919
}
2020

2121
interface Content extends ContentSize {
@@ -41,12 +41,11 @@ export default function ParquetView({ source, setProgress, setError }: ViewerPro
4141
const from = { url: resolveUrl, byteLength: asyncBuffer.byteLength, requestInit }
4242
setProgress(0.66)
4343
const metadata = await parquetMetadataAsync(asyncBuffer)
44-
let dataframe = parquetDataFrame(from, metadata)
45-
dataframe = rowCache(dataframe)
44+
const dataframe = parquetDataFrame(from, metadata)
4645
const fileSize = asyncBuffer.byteLength
4746
setContent({ dataframe, fileSize })
4847
} catch (error) {
49-
setError(error as Error)
48+
setError(error)
5049
} finally {
5150
setIsLoading(false)
5251
setProgress(1)
@@ -83,9 +82,14 @@ export default function ParquetView({ source, setProgress, setError }: ViewerPro
8382
if (cell?.col === col && cell.row === row) {
8483
return undefined
8584
}
85+
const columnName = content?.dataframe.header[col]
86+
if (columnName === undefined || !content?.dataframe.getCell({ row, column: columnName })) {
87+
// don't open the cell panel until it has loaded
88+
return undefined
89+
}
8690
return { row, col }
8791
})
88-
}, [])
92+
}, [content])
8993
const onDoubleClickCell = useCallback((_event: React.MouseEvent, col: number, row: number) => {
9094
toggleCell(col, row)
9195
}, [toggleCell])

src/components/TextView/TextView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import styles from './TextView.module.css'
77

88
interface ViewerProps {
99
source: FileSource
10-
setError: (error: Error | undefined) => void
10+
setError: (error: unknown) => void
1111
}
1212

1313
/**
@@ -36,7 +36,7 @@ export default function TextView({ source, setError }: ViewerProps) {
3636
setError(undefined)
3737
setContent({ text, fileSize })
3838
} catch (error) {
39-
setError(error as Error)
39+
setError(error)
4040
setContent(undefined)
4141
} finally {
4242
setIsLoading(false)

0 commit comments

Comments
 (0)