Skip to content

Commit 48c13ce

Browse files
committed
fix dropzone
1 parent 5251dba commit 48c13ce

File tree

2 files changed

+105
-101
lines changed

2 files changed

+105
-101
lines changed

apps/hyparquet-demo/src/App.tsx

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,85 @@
11
import { ReactNode } from 'react'
2-
import Page from './Page.js'
2+
import Page, { PageProps } from './Page.js'
33
import Welcome from './Welcome.js'
44

5+
import { DataFrame, rowCache } from 'hightable'
6+
import { FileMetaData, byteLengthFromUrl, parquetMetadataAsync, parquetSchema } from 'hyparquet'
7+
import { useCallback, useEffect, useState } from 'react'
8+
import Dropzone from './Dropzone.js'
9+
import Layout from './Layout.js'
10+
import { asyncBufferFrom } from './utils.js'
11+
import { parquetQueryWorker } from './workers/parquetWorkerClient.js'
12+
import { AsyncBufferFrom, Row } from './workers/types.js'
13+
514
export default function App(): ReactNode {
615
const params = new URLSearchParams(location.search)
716
const url = params.get('key') ?? undefined
8-
return url ? <Page url={url} /> : <Welcome />
17+
18+
const [error, setError] = useState<Error>()
19+
const [pageProps, setPageProps] = useState<PageProps>()
20+
21+
const setUnknownError = useCallback((e: unknown) => {
22+
setError(e instanceof Error ? e : new Error(String(e)))
23+
}, [])
24+
25+
const onUrlDrop = useCallback(
26+
(url: string) => {
27+
// Add key=url to query string
28+
const params = new URLSearchParams(location.search)
29+
params.set('key', url)
30+
history.pushState({}, '', `${location.pathname}?${params}`)
31+
byteLengthFromUrl(url).then(byteLength => setAsyncBuffer(url, { url, byteLength })).catch(setUnknownError)
32+
},
33+
[setUnknownError],
34+
)
35+
36+
useEffect(() => {
37+
if (!pageProps && url) {
38+
onUrlDrop(url)
39+
}
40+
}, [ url, pageProps, onUrlDrop])
41+
42+
function onFileDrop(file: File) {
43+
// Clear query string
44+
history.pushState({}, '', location.pathname)
45+
setAsyncBuffer(file.name, { file, byteLength: file.size }).catch(setUnknownError)
46+
}
47+
48+
async function setAsyncBuffer(name: string, from: AsyncBufferFrom) {
49+
const asyncBuffer = await asyncBufferFrom(from)
50+
const metadata = await parquetMetadataAsync(asyncBuffer)
51+
const df = rowCache(parquetDataFrame(from, metadata))
52+
setPageProps({ metadata, df, name, byteLength: from.byteLength, setError })
53+
}
54+
55+
return <Layout error={error}>
56+
<Dropzone
57+
onError={(e) => { setError(e) }}
58+
onFileDrop={onFileDrop}
59+
onUrlDrop={onUrlDrop}>
60+
{pageProps ? <Page {...pageProps} /> : <Welcome />}
61+
</Dropzone>
62+
</Layout>
63+
}
64+
65+
/**
66+
* Convert a parquet file into a dataframe.
67+
*/
68+
function parquetDataFrame(from: AsyncBufferFrom, metadata: FileMetaData): DataFrame {
69+
const { children } = parquetSchema(metadata)
70+
return {
71+
header: children.map(child => child.element.name),
72+
numRows: Number(metadata.num_rows),
73+
/**
74+
* @param {number} rowStart
75+
* @param {number} rowEnd
76+
* @param {string} orderBy
77+
* @returns {Promise<any[][]>}
78+
*/
79+
rows(rowStart: number, rowEnd: number, orderBy: string): Promise<Row[]> {
80+
console.log(`reading rows ${rowStart}-${rowEnd}`, orderBy)
81+
return parquetQueryWorker({ from, metadata, rowStart, rowEnd, orderBy })
82+
},
83+
sortable: true,
84+
}
985
}

apps/hyparquet-demo/src/Page.tsx

Lines changed: 27 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,43 @@
1-
import HighTable, { DataFrame, rowCache } from 'hightable'
2-
import { FileMetaData, byteLengthFromUrl, parquetMetadataAsync, parquetSchema } from 'hyparquet'
3-
import { ReactNode, useCallback, useEffect, useState } from 'react'
1+
import HighTable, { DataFrame } from 'hightable'
2+
import { FileMetaData } from 'hyparquet'
3+
import { ReactNode, useState } from 'react'
44
import Dropdown from './Dropdown.js'
5-
import Dropzone from './Dropzone.js'
6-
import Layout from './Layout.js'
75
import ParquetLayout from './ParquetLayout.js'
86
import ParquetMetadata from './ParquetMetadata.js'
9-
import { asyncBufferFrom } from './utils.js'
10-
import { parquetQueryWorker } from './workers/parquetWorkerClient.js'
11-
import { AsyncBufferFrom, Row } from './workers/types.js'
127

138
type Lens = 'table' | 'metadata' | 'layout'
149

10+
export interface PageProps {
11+
metadata: FileMetaData
12+
df: DataFrame
13+
name: string
14+
byteLength?: number
15+
setError: (e: Error) => void
16+
}
17+
1518
/**
1619
* Hyparquet demo viewer page
1720
* @param {Object} props
18-
* @param {string} [props.url]
1921
* @returns {ReactNode}
2022
*/
21-
export default function Page({ url }: { url?: string }): ReactNode {
22-
const [error, setError] = useState<Error>()
23-
const [df, setDf] = useState<DataFrame>()
24-
const [name, setName] = useState<string>()
23+
export default function Page({ metadata, df, name, byteLength, setError }: PageProps): ReactNode {
2524
const [lens, setLens] = useState<Lens>('table')
26-
const [metadata, setMetadata] = useState<FileMetaData>()
27-
const [byteLength, setByteLength] = useState<number>()
28-
29-
const setUnknownError = useCallback((e: unknown) => {
30-
setError(e instanceof Error ? e : new Error(String(e)))
31-
}, [])
32-
33-
const onUrlDrop = useCallback(
34-
(url: string) => {
35-
// Add key=url to query string
36-
const params = new URLSearchParams(location.search)
37-
params.set('key', url)
38-
history.pushState({}, '', `${location.pathname}?${params}`)
39-
byteLengthFromUrl(url).then(byteLength => setAsyncBuffer(url, { url, byteLength })).catch(setUnknownError)
40-
},
41-
[setUnknownError],
42-
)
43-
44-
useEffect(() => {
45-
if (!df && url) {
46-
onUrlDrop(url)
47-
}
48-
}, [ url, df, onUrlDrop])
49-
50-
function onFileDrop(file: File) {
51-
// Clear query string
52-
history.pushState({}, '', location.pathname)
53-
setAsyncBuffer(file.name, { file, byteLength: file.size }).catch(setUnknownError)
54-
}
5525

56-
async function setAsyncBuffer(name: string, from: AsyncBufferFrom) {
57-
// TODO: Replace welcome with spinner
58-
const asyncBuffer = await asyncBufferFrom(from)
59-
const metadata = await parquetMetadataAsync(asyncBuffer)
60-
setMetadata(metadata)
61-
setName(name)
62-
setByteLength(from.byteLength)
63-
let df = parquetDataFrame(from, metadata)
64-
df = rowCache(df)
65-
setDf(df)
66-
document.getElementById('welcome')?.remove()
67-
}
68-
69-
return <Layout error={error}>
70-
<Dropzone
71-
onError={(e) => { setError(e) }}
72-
onFileDrop={onFileDrop}
73-
onUrlDrop={onUrlDrop}>
74-
{metadata && df && <>
75-
<div className='top-header'>{name}</div>
76-
<div className='view-header'>
77-
{byteLength !== undefined && <span title={byteLength.toLocaleString() + ' bytes'}>{formatFileSize(byteLength)}</span>}
78-
<span>{df.numRows.toLocaleString()} rows</span>
79-
<Dropdown label={lens}>
80-
<button onClick={() => { setLens('table') }}>Table</button>
81-
<button onClick={() => { setLens('metadata') }}>Metadata</button>
82-
{byteLength && <button onClick={() => { setLens('layout') }}>Layout</button>}
83-
</Dropdown>
84-
</div>
85-
{lens === 'table' && <HighTable cacheKey={name} data={df} onError={setError} />}
86-
{lens === 'metadata' && <ParquetMetadata metadata={metadata} />}
87-
{lens === 'layout' && byteLength && <ParquetLayout byteLength={byteLength} metadata={metadata} />}
88-
</>}
89-
</Dropzone>
90-
</Layout>
91-
}
92-
93-
/**
94-
* Convert a parquet file into a dataframe.
95-
*/
96-
function parquetDataFrame(from: AsyncBufferFrom, metadata: FileMetaData): DataFrame {
97-
const { children } = parquetSchema(metadata)
98-
return {
99-
header: children.map(child => child.element.name),
100-
numRows: Number(metadata.num_rows),
101-
/**
102-
* @param {number} rowStart
103-
* @param {number} rowEnd
104-
* @param {string} orderBy
105-
* @returns {Promise<any[][]>}
106-
*/
107-
rows(rowStart: number, rowEnd: number, orderBy: string): Promise<Row[]> {
108-
console.log(`reading rows ${rowStart}-${rowEnd}`, orderBy)
109-
return parquetQueryWorker({ from, metadata, rowStart, rowEnd, orderBy })
110-
},
111-
sortable: true,
112-
}
26+
return <>
27+
<div className='top-header'>{name}</div>
28+
<div className='view-header'>
29+
{byteLength !== undefined && <span title={byteLength.toLocaleString() + ' bytes'}>{formatFileSize(byteLength)}</span>}
30+
<span>{df.numRows.toLocaleString()} rows</span>
31+
<Dropdown label={lens}>
32+
<button onClick={() => { setLens('table') }}>Table</button>
33+
<button onClick={() => { setLens('metadata') }}>Metadata</button>
34+
{byteLength && <button onClick={() => { setLens('layout') }}>Layout</button>}
35+
</Dropdown>
36+
</div>
37+
{lens === 'table' && <HighTable cacheKey={name} data={df} onError={setError} />}
38+
{lens === 'metadata' && <ParquetMetadata metadata={metadata} />}
39+
{lens === 'layout' && byteLength && <ParquetLayout byteLength={byteLength} metadata={metadata} />}
40+
</>
11341
}
11442

11543
/**

0 commit comments

Comments
 (0)