Skip to content

Commit db6be39

Browse files
committed
move html to Welcome.tsx + fix p > ul
1 parent f0c371e commit db6be39

File tree

5 files changed

+199
-184
lines changed

5 files changed

+199
-184
lines changed

apps/hyparquet-demo/index.html

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -19,59 +19,6 @@
1919
</nav>
2020
<main id="content">
2121
<div id="app"></div>
22-
<div id="welcome">
23-
<h1>hyparquet</h1>
24-
<sub>
25-
/haɪ pɑːrˈkeɪ/
26-
<img src="demo/assets/audio.svg" alt="play hyparquet pronunciation" height="18" width="18" onclick="audio.play()">
27-
</sub>
28-
<audio id="audio" src="demo/assets/hyparquet.mp3"></audio>
29-
<h2>in-browser parquet file reader</h2>
30-
<p>
31-
<a href="https://www.npmjs.com/package/hyparquet"><img src="https://img.shields.io/npm/v/hyparquet" alt="npm hyparquet"></a>
32-
<a href="https://github.com/hyparam/hyparquet"><img src="https://img.shields.io/github/stars/hyparam/hyparquet?style=social" alt="star hyparquet"></a>
33-
</p>
34-
<p>
35-
Online demo of <a href="https://github.com/hyparam/hyparquet">hyparquet</a>: a parser for apache parquet files.
36-
Uses <a href="https://github.com/hyparam/hightable">hightable</a> for high performance windowed table viewing.
37-
</p>
38-
<p>
39-
Drag and drop a parquet file (or url) to see your parquet data. 👀
40-
</p>
41-
<p>
42-
Example files:
43-
<ul class="quick-links">
44-
<li>
45-
<a
46-
class="aws"
47-
href="?key=https://hyperparam-public.s3.amazonaws.com/wiki-en-00000-of-00041.parquet">
48-
s3://wiki-en-00000-of-00041.parquet
49-
</a>
50-
</li>
51-
<li>
52-
<a
53-
class="azure"
54-
href="?key=https://hyperparam.blob.core.windows.net/hyperparam/starcoderdata-js-00000-of-00065.parquet">
55-
azure://starcoderdata-js-00000-of-00065.parquet
56-
</a>
57-
</li>
58-
<li>
59-
<a
60-
class="huggingface"
61-
href="?key=https://huggingface.co/datasets/codeparrot/github-code/resolve/main/data/train-00000-of-01126.parquet?download=true">
62-
huggingface://github-code-00000-of-01126.parquet
63-
</a>
64-
</li>
65-
<li>
66-
<a
67-
class="github"
68-
href="?key=https://raw.githubusercontent.com/hyparam/hyparquet/master/test/files/rowgroups.parquet">
69-
github://rowgroups.parquet
70-
</a>
71-
</li>
72-
</ul>
73-
</p>
74-
</div>
7522
</main>
7623
<input id="file-input" type="file">
7724

apps/hyparquet-demo/src/App.tsx

Lines changed: 8 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,9 @@
1-
import HighTable, { DataFrame, rowCache } from 'hightable'
2-
import { FileMetaData, byteLengthFromUrl, parquetMetadataAsync, parquetSchema } from 'hyparquet'
3-
import { ReactNode, useCallback, useEffect, useState } from 'react'
4-
import Dropdown from './Dropdown.js'
5-
import Dropzone from './Dropzone.js'
6-
import Layout from './Layout.js'
7-
import ParquetLayout from './ParquetLayout.js'
8-
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'
12-
13-
type Lens = 'table' | 'metadata' | 'layout'
14-
15-
/**
16-
* Hyparquet demo viewer page
17-
* @param {Object} props
18-
* @param {string} [props.url]
19-
* @returns {ReactNode}
20-
*/
21-
export default function App({ url }: { url?: string }): ReactNode {
22-
const [error, setError] = useState<Error>()
23-
const [df, setDf] = useState<DataFrame>()
24-
const [name, setName] = useState<string>()
25-
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-
}
55-
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-
}
113-
}
114-
115-
/**
116-
* Returns the file size in human readable format.
117-
*
118-
* @param {number} bytes file size in bytes
119-
* @returns {string} formatted file size string
120-
*/
121-
function formatFileSize(bytes: number): string {
122-
const sizes = ['b', 'kb', 'mb', 'gb', 'tb']
123-
if (bytes === 0) return '0 b'
124-
const i = Math.floor(Math.log2(bytes) / 10)
125-
if (i === 0) return `${bytes} b`
126-
const base = bytes / Math.pow(1024, i)
127-
return `${base < 10 ? base.toFixed(1) : Math.round(base)} ${sizes[i]}`
1+
import { ReactNode } from 'react'
2+
import Page from './Page.js'
3+
import Welcome from './Welcome.js'
4+
5+
export default function App(): ReactNode {
6+
const params = new URLSearchParams(location.search)
7+
const url = params.get('key') ?? undefined
8+
return url ? <Page url={url} /> : <Welcome />
1289
}

apps/hyparquet-demo/src/Page.tsx

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import HighTable, { DataFrame, rowCache } from 'hightable'
2+
import { FileMetaData, byteLengthFromUrl, parquetMetadataAsync, parquetSchema } from 'hyparquet'
3+
import { ReactNode, useCallback, useEffect, useState } from 'react'
4+
import Dropdown from './Dropdown.js'
5+
import Dropzone from './Dropzone.js'
6+
import Layout from './Layout.js'
7+
import ParquetLayout from './ParquetLayout.js'
8+
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'
12+
13+
type Lens = 'table' | 'metadata' | 'layout'
14+
15+
/**
16+
* Hyparquet demo viewer page
17+
* @param {Object} props
18+
* @param {string} [props.url]
19+
* @returns {ReactNode}
20+
*/
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>()
25+
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+
}
55+
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+
}
113+
}
114+
115+
/**
116+
* Returns the file size in human readable format.
117+
*
118+
* @param {number} bytes file size in bytes
119+
* @returns {string} formatted file size string
120+
*/
121+
function formatFileSize(bytes: number): string {
122+
const sizes = ['b', 'kb', 'mb', 'gb', 'tb']
123+
if (bytes === 0) return '0 b'
124+
const i = Math.floor(Math.log2(bytes) / 10)
125+
if (i === 0) return `${bytes} b`
126+
const base = bytes / Math.pow(1024, i)
127+
return `${base < 10 ? base.toFixed(1) : Math.round(base)} ${sizes[i]}`
128+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { ReactNode, useRef } from 'react'
2+
import audioSvg from './assets/audio.svg'
3+
import hyparquetMp3 from './assets/hyparquet.mp3'
4+
5+
6+
export default function Welcome(): ReactNode {
7+
const audio = useRef<HTMLAudioElement>(null)
8+
9+
return <div id="welcome">
10+
<h1>hyparquet</h1>
11+
<sub>img
12+
/haɪ pɑːrˈkeɪ/
13+
<img src={audioSvg} alt="play hyparquet pronunciation" height="18" width="18" onClick={() => {audio.current?.play().catch(() => { return undefined})}} />
14+
</sub>
15+
<audio ref={audio} id="audio" src={hyparquetMp3}></audio>
16+
<h2>in-browser parquet file reader</h2>
17+
<p>
18+
<a href="https://www.npmjs.com/package/hyparquet"><img src="https://img.shields.io/npm/v/hyparquet" alt="npm hyparquet" /></a>
19+
<a href="https://github.com/hyparam/hyparquet"><img src="https://img.shields.io/github/stars/hyparam/hyparquet?style=social" alt="star hyparquet" /></a>
20+
</p>
21+
<p>
22+
Online demo of <a href="https://github.com/hyparam/hyparquet">hyparquet</a>: a parser for apache parquet files.
23+
Uses <a href="https://github.com/hyparam/hightable">hightable</a> for high performance windowed table viewing.
24+
</p>
25+
<p>
26+
Drag and drop a parquet file (or url) to see your parquet data. 👀
27+
</p>
28+
<p>
29+
Example files:
30+
</p>
31+
<ul className="quick-links">
32+
<li>
33+
<a
34+
className="aws"
35+
href="?key=https://hyperparam-public.s3.amazonaws.com/wiki-en-00000-of-00041.parquet">
36+
s3://wiki-en-00000-of-00041.parquet
37+
</a>
38+
</li>
39+
<li>
40+
<a
41+
className="azure"
42+
href="?key=https://hyperparam.blob.core.windows.net/hyperparam/starcoderdata-js-00000-of-00065.parquet">
43+
azure://starcoderdata-js-00000-of-00065.parquet
44+
</a>
45+
</li>
46+
<li>
47+
<a
48+
className="huggingface"
49+
href="?key=https://huggingface.co/datasets/codeparrot/github-code/resolve/main/data/train-00000-of-01126.parquet?download=true">
50+
huggingface://github-code-00000-of-01126.parquet
51+
</a>
52+
</li>
53+
<li>
54+
<a
55+
className="github"
56+
href="?key=https://raw.githubusercontent.com/hyparam/hyparquet/master/test/files/rowgroups.parquet">
57+
github://rowgroups.parquet
58+
</a>
59+
</li>
60+
</ul>
61+
</div>
62+
}

apps/hyparquet-demo/src/main.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ import './index.css'
66
const app = document.getElementById('app')
77
if (!app) throw new Error('missing app element')
88

9-
const params = new URLSearchParams(location.search)
10-
const url = params.get('key') ?? undefined
11-
129
ReactDOM.createRoot(app).render(<StrictMode>
13-
<App url={url} />
10+
<App />
1411
</StrictMode>)

0 commit comments

Comments
 (0)