Skip to content

Commit e4ff6e8

Browse files
authored
Use a global Config context instead of passing props along all the components (#187)
* Use a global Config context instead of passing props along all the components * comment
1 parent 8eedb0b commit e4ff6e8

File tree

18 files changed

+166
-135
lines changed

18 files changed

+166
-135
lines changed

src/components/App.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useMemo } from 'react'
2+
import { Config, ConfigProvider } from '../hooks/useConfig.js'
13
import { getHttpSource } from '../lib/sources/httpSource.js'
24
import { getHyperparamSource } from '../lib/sources/hyperparamSource.js'
35
import Page from './Page.js'
@@ -10,20 +12,20 @@ export default function App() {
1012

1113
const source = getHttpSource(sourceId) ?? getHyperparamSource(sourceId, { endpoint: location.origin })
1214

15+
// Memoize the config to avoid creating a new object on each render
16+
const config: Config = useMemo(() => ({
17+
routes: {
18+
getSourceRouteUrl: ({ sourceId }) => `/files?key=${sourceId}`,
19+
getCellRouteUrl: ({ sourceId, col, row }) => `/files?key=${sourceId}&col=${col}&row=${row}`,
20+
},
21+
}), [])
22+
1323
if (!source) {
1424
return <div>Could not load a data source. You have to pass a valid source in the url.</div>
1525
}
1626
return (
17-
<Page
18-
source={source}
19-
navigation={{ row, col }}
20-
config={{
21-
slidePanel: {},
22-
routes: {
23-
getSourceRouteUrl: ({ sourceId }) => `/files?key=${sourceId}`,
24-
getCellRouteUrl: ({ sourceId, col, row }) => `/files?key=${sourceId}&col=${col}&row=${row}`,
25-
},
26-
}}
27-
/>
27+
<ConfigProvider value={config}>
28+
<Page source={source} navigation={{ row, col }} />
29+
</ConfigProvider>
2830
)
2931
}

src/components/Breadcrumb.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import type { ReactNode } from 'react'
2-
import type { RoutesConfig } from '../lib/routes.js'
2+
import { useConfig } from '../hooks/useConfig.js'
33
import type { Source } from '../lib/sources/types.js'
44

5-
export type BreadcrumbConfig = RoutesConfig
65
interface BreadcrumbProps {
76
source: Source,
8-
config?: BreadcrumbConfig
97
children?: ReactNode
108
}
119

1210
/**
1311
* Breadcrumb navigation
1412
*/
15-
export default function Breadcrumb({ source, config, children }: BreadcrumbProps) {
13+
export default function Breadcrumb({ source, children }: BreadcrumbProps) {
14+
const { routes } = useConfig()
15+
1616
return <nav className='top-header top-header-divided'>
1717
<div className='path'>
1818
{source.sourceParts.map((part, depth) =>
19-
<a href={config?.routes?.getSourceRouteUrl?.({ sourceId: part.sourceId }) ?? ''} key={depth}>{part.text}</a>
19+
<a href={routes?.getSourceRouteUrl?.({ sourceId: part.sourceId }) ?? ''} key={depth}>{part.text}</a>
2020
)}
2121
</div>
2222
{children}

src/components/Cell.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,19 @@ import { asyncBufferFromUrl, parquetMetadataAsync } from 'hyparquet'
33
import { useEffect, useState } from 'react'
44
import type { FileSource } from '../lib/sources/types.js'
55
import { parquetDataFrame } from '../lib/tableProvider.js'
6-
import Breadcrumb, { BreadcrumbConfig } from './Breadcrumb.js'
6+
import Breadcrumb from './Breadcrumb.js'
77
import Layout from './Layout.js'
88

9-
export type CellConfig = BreadcrumbConfig
10-
119
interface CellProps {
1210
source: FileSource;
1311
row: number;
1412
col: number;
15-
config?: CellConfig
1613
}
1714

1815
/**
1916
* Cell viewer displays a single cell from a table.
2017
*/
21-
export default function CellView({ source, row, col, config }: CellProps) {
18+
export default function CellView({ source, row, col }: CellProps) {
2219
const [text, setText] = useState<string | undefined>()
2320
const [progress, setProgress] = useState<number>()
2421
const [error, setError] = useState<Error>()
@@ -69,7 +66,7 @@ export default function CellView({ source, row, col, config }: CellProps) {
6966

7067
return (
7168
<Layout progress={progress} error={error} title={fileName}>
72-
<Breadcrumb source={source} config={config} />
69+
<Breadcrumb source={source} />
7370

7471
{/* <Highlight text={text || ''} /> */}
7572
<pre className="viewer text">{text}</pre>

src/components/File.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
import { useState } from 'react'
22
import type { FileSource } from '../lib/sources/types.js'
3-
import Breadcrumb, { BreadcrumbConfig } from './Breadcrumb.js'
3+
import Breadcrumb from './Breadcrumb.js'
44
import Layout from './Layout.js'
5-
import Viewer, { ViewerConfig } from './viewers/Viewer.js'
6-
7-
export type FileConfig = ViewerConfig & BreadcrumbConfig
5+
import Viewer from './viewers/Viewer.js'
86

97
interface FileProps {
108
source: FileSource
11-
config?: FileConfig
129
}
1310

1411
/**
1512
* File viewer page
1613
*/
17-
export default function File({ source, config }: FileProps) {
14+
export default function File({ source }: FileProps) {
1815
const [progress, setProgress] = useState<number>()
1916
const [error, setError] = useState<Error>()
2017

2118
return <Layout progress={progress} error={error} title={source.fileName}>
22-
<Breadcrumb source={source} config={config} />
23-
<Viewer source={source} setProgress={setProgress} setError={setError} config={config} />
19+
<Breadcrumb source={source} />
20+
<Viewer source={source} setProgress={setProgress} setError={setError} />
2421
</Layout>
2522
}

src/components/Folder.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
import { useEffect, useRef, useState } from 'react'
2+
import { useConfig } from '../hooks/useConfig.js'
23
import type { DirSource, FileMetadata } from '../lib/sources/types.js'
34
import { cn, formatFileSize, getFileDate, getFileDateShort } from '../lib/utils.js'
4-
import Breadcrumb, { BreadcrumbConfig } from './Breadcrumb.js'
5+
import Breadcrumb from './Breadcrumb.js'
56
import Layout, { Spinner } from './Layout.js'
67

7-
export type FolderConfig = BreadcrumbConfig
8-
98
interface FolderProps {
109
source: DirSource
11-
config?: FolderConfig
1210
}
1311

1412
/**
1513
* Folder browser page
1614
*/
17-
export default function Folder({ source, config }: FolderProps) {
15+
export default function Folder({ source }: FolderProps) {
1816
// State to hold file listing
1917
const [files, setFiles] = useState<FileMetadata[]>()
2018
const [error, setError] = useState<Error>()
2119
const [searchQuery, setSearchQuery] = useState('')
2220
const searchRef = useRef<HTMLInputElement>(null)
2321
const listRef = useRef<HTMLUListElement>(null)
2422

23+
const { routes } = useConfig()
24+
2525
// Fetch files on component mount
2626
useEffect(() => {
2727
source.listFiles()
@@ -83,7 +83,7 @@ export default function Folder({ source, config }: FolderProps) {
8383
}, [])
8484

8585
return <Layout error={error} title={source.prefix}>
86-
<Breadcrumb source={source} config={config}>
86+
<Breadcrumb source={source}>
8787
<div className='top-actions'>
8888
<input autoFocus className='search' placeholder='Search...' ref={searchRef} />
8989
</div>
@@ -92,7 +92,7 @@ export default function Folder({ source, config }: FolderProps) {
9292
{files && files.length > 0 && <ul className='file-list' ref={listRef}>
9393
{filtered?.map((file, index) =>
9494
<li key={index}>
95-
<a href={config?.routes?.getSourceRouteUrl?.({ sourceId: file.sourceId }) ?? location.href}>
95+
<a href={routes?.getSourceRouteUrl?.({ sourceId: file.sourceId }) ?? location.href}>
9696
<span className={cn('file-name', 'file', file.kind === 'directory' && 'folder')}>
9797
{file.name}
9898
</span>

src/components/Page.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Source } from '../lib/sources/types.js'
22
import Cell from './Cell.js'
3-
import File, { FileConfig } from './File.js'
3+
import File from './File.js'
44
import Folder from './Folder.js'
55

6-
export type PageConfig = FileConfig
76
export interface Navigation {
87
col?: number
98
row?: number
@@ -12,18 +11,17 @@ export interface Navigation {
1211
interface PageProps {
1312
source: Source,
1413
navigation?: Navigation,
15-
config?: PageConfig
1614
}
1715

18-
export default function Page({ source, navigation, config }: PageProps) {
16+
export default function Page({ source, navigation }: PageProps) {
1917
if (source.kind === 'directory') {
20-
return <Folder source={source} config={config}/>
18+
return <Folder source={source} />
2119
}
2220
if (navigation?.row !== undefined && navigation.col !== undefined) {
2321
// cell view
24-
return <Cell source={source} row={navigation.row} col={navigation.col} config={config} />
22+
return <Cell source={source} row={navigation.row} col={navigation.col} />
2523
} else {
2624
// file view
27-
return <File source={source} config={config} />
25+
return <File source={source} />
2826
}
2927
}

src/components/index.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import Breadcrumb, { BreadcrumbConfig } from './Breadcrumb.js'
2-
import Cell, { CellConfig } from './Cell.js'
3-
import File, { FileConfig } from './File.js'
4-
import Folder, { FolderConfig } from './Folder.js'
1+
import Breadcrumb from './Breadcrumb.js'
2+
import Cell from './Cell.js'
3+
import File from './File.js'
4+
import Folder from './Folder.js'
55
import Layout, { ErrorBar, Spinner } from './Layout.js'
66
import Markdown from './Markdown.js'
7-
import Page, { PageConfig } from './Page.js'
7+
import Page from './Page.js'
88
export * from './viewers/index.js'
99
export { Breadcrumb, Cell, ErrorBar, File, Folder, Layout, Markdown, Page, Spinner }
10-
export type { BreadcrumbConfig, CellConfig, FileConfig, FolderConfig, PageConfig }

src/components/viewers/ParquetView.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
11
import HighTable, { DataFrame, rowCache } from 'hightable'
22
import { asyncBufferFromUrl, parquetMetadataAsync } from 'hyparquet'
33
import React, { useCallback, useEffect, useState } from 'react'
4-
import { RoutesConfig, appendSearchParams } from '../../lib/routes.js'
4+
import { useConfig } from '../../hooks/useConfig.js'
5+
import { appendSearchParams } from '../../lib/routes.js'
56
import { FileSource } from '../../lib/sources/types.js'
67
import { parquetDataFrame } from '../../lib/tableProvider.js'
78
import { cn } from '../../lib/utils.js'
89
import styles from '../../styles/ParquetView.module.css'
910
import { Spinner } from '../Layout.js'
1011
import CellPanel from './CellPanel.js'
1112
import ContentHeader, { ContentSize } from './ContentHeader.js'
12-
import SlidePanel, { SlidePanelConfig } from './SlidePanel.js'
13-
14-
interface HighTableConfig {
15-
hightable?: {
16-
className?: string;
17-
}
18-
}
19-
export type ParquetViewConfig = SlidePanelConfig & RoutesConfig & HighTableConfig
13+
import SlidePanel from './SlidePanel.js'
2014

2115
interface ViewerProps {
2216
source: FileSource
2317
setProgress: (progress: number | undefined) => void
2418
setError: (error: Error | undefined) => void
25-
config?: ParquetViewConfig
2619
}
2720

2821
interface Content extends ContentSize {
@@ -32,10 +25,11 @@ interface Content extends ContentSize {
3225
/**
3326
* Parquet file viewer
3427
*/
35-
export default function ParquetView({ source, setProgress, setError, config }: ViewerProps) {
28+
export default function ParquetView({ source, setProgress, setError }: ViewerProps) {
3629
const [isLoading, setIsLoading] = useState<boolean>(true)
3730
const [content, setContent] = useState<Content>()
3831
const [cell, setCell] = useState<{ row: number, col: number } | undefined>()
32+
const { highTable, routes } = useConfig()
3933

4034
useEffect(() => {
4135
async function loadParquetDataFrame() {
@@ -77,12 +71,12 @@ export default function ParquetView({ source, setProgress, setError, config }: V
7771

7872
const { sourceId } = source
7973
const getCellRouteUrl = useCallback(({ col, row }: {col: number, row: number}) => {
80-
const url = config?.routes?.getCellRouteUrl?.({ sourceId, col, row })
74+
const url = routes?.getCellRouteUrl?.({ sourceId, col, row })
8175
if (url) {
8276
return url
8377
}
8478
return appendSearchParams({ col: col.toString(), row: row.toString() })
85-
}, [config, sourceId])
79+
}, [routes, sourceId])
8680

8781
const onDoubleClickCell = useCallback((_event: React.MouseEvent, col: number, row: number) => {
8882
if (cell?.col === col && cell.row === row) {
@@ -108,7 +102,7 @@ export default function ParquetView({ source, setProgress, setError, config }: V
108102
onDoubleClickCell={onDoubleClickCell}
109103
onMouseDownCell={onMouseDownCell}
110104
onError={setError}
111-
className={cn(styles.hightable, config?.hightable?.className)}
105+
className={cn(styles.hightable, highTable?.className)}
112106
/>}
113107

114108
{isLoading && <div className='center'><Spinner /></div>}
@@ -132,7 +126,6 @@ export default function ParquetView({ source, setProgress, setError, config }: V
132126
isPanelOpen={!!(content?.dataframe && cell)}
133127
mainContent={mainContent}
134128
panelContent={panelContent}
135-
config={config}
136129
/>
137130
)
138131
}

src/components/viewers/SlidePanel.tsx

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
import React, { ReactNode, useCallback, useEffect, useState } from 'react'
2-
3-
export interface SlidePanelConfig {
4-
slidePanel?: {
5-
minWidth?: number
6-
defaultWidth?: number
7-
}
8-
}
2+
import { useConfig } from '../../hooks/useConfig.js'
93

104
interface SlidePanelProps {
115
mainContent: ReactNode
126
panelContent: ReactNode
137
isPanelOpen: boolean
14-
config?: SlidePanelConfig
158
}
169

1710
const WIDTH = {
@@ -22,17 +15,16 @@ const WIDTH = {
2215
/**
2316
* Slide out panel component with resizing.
2417
*/
25-
export default function SlidePanel({
26-
mainContent, panelContent, isPanelOpen, config,
27-
}: SlidePanelProps) {
28-
const minWidth = config?.slidePanel?.minWidth && config.slidePanel.minWidth > 0 ? config.slidePanel.minWidth : WIDTH.MIN
18+
export default function SlidePanel({ mainContent, panelContent, isPanelOpen }: SlidePanelProps) {
19+
const { slidePanel } = useConfig()
20+
const minWidth = slidePanel?.minWidth && slidePanel.minWidth > 0 ? slidePanel.minWidth : WIDTH.MIN
2921
function validWidth(width?: number): number | undefined {
3022
if (width && minWidth <= width) {
3123
return width
3224
}
3325
return undefined
3426
}
35-
const defaultWidth = validWidth(config?.slidePanel?.defaultWidth) ?? WIDTH.DEFAULT
27+
const defaultWidth = validWidth(slidePanel?.defaultWidth) ?? WIDTH.DEFAULT
3628
const [resizingClientX, setResizingClientX] = useState(-1)
3729
const panelRef = React.createRef<HTMLDivElement>()
3830

src/components/viewers/Viewer.tsx

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,25 @@ import AvroView from './AvroView.js'
44
import ImageView from './ImageView.js'
55
import JsonView from './JsonView.js'
66
import MarkdownView from './MarkdownView.js'
7-
import TableView, { ParquetViewConfig } from './ParquetView.js'
7+
import TableView from './ParquetView.js'
88
import TextView from './TextView.js'
99

10-
export type ViewerConfig = ParquetViewConfig
11-
1210
interface ViewerProps {
1311
source: FileSource;
1412
setError: (error: Error | undefined) => void;
1513
setProgress: (progress: number | undefined) => void;
16-
config?: ViewerConfig;
1714
}
1815

1916
/**
2017
* Get a viewer for a file.
2118
* Chooses viewer based on content type.
2219
*/
23-
export default function Viewer({
24-
source,
25-
setError,
26-
setProgress,
27-
config,
28-
}: ViewerProps) {
20+
export default function Viewer({ source, setError, setProgress }: ViewerProps) {
2921
const { fileName } = source
3022
if (fileName.endsWith('.md')) {
3123
return <MarkdownView source={source} setError={setError} />
3224
} else if (fileName.endsWith('.parquet')) {
33-
return (
34-
<TableView
35-
source={source}
36-
setError={setError}
37-
setProgress={setProgress}
38-
config={config}
39-
/>
40-
)
25+
return <TableView source={source} setError={setError} setProgress={setProgress} />
4126
} else if (fileName.endsWith('.json')) {
4227
return <JsonView source={source} setError={setError} />
4328
} else if (fileName.endsWith('.avro')) {

0 commit comments

Comments
 (0)