Skip to content

Commit

Permalink
Unify url view
Browse files Browse the repository at this point in the history
  • Loading branch information
platypii committed Jun 8, 2024
1 parent 8591076 commit b54659c
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 148 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_modules
.vscode
*.parquet
/coverage/
.DS_Store
10 changes: 5 additions & 5 deletions public/build/render.js

Large diffs are not rendered by default.

25 changes: 16 additions & 9 deletions src/components/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ export default function CellView() {
const [error, setError] = useState<Error>()

// File path from url
const path = location.pathname.split('/')
const key = decodeURI(path.slice(2).join('/'))
const search = new URLSearchParams(location.search)
const key = decodeURIComponent(search.get('key') || '').replace(/\/$/, '')
const path = key.split('/')
const isUrl = key.startsWith('http://') || key.startsWith('https://')
const url = isUrl ? key : '/api/store/get?key=' + key

// row, col from url
const search = new URLSearchParams(location.search)
const row = Number(search.get('row'))
const col = Number(search.get('col'))

Expand All @@ -42,7 +44,7 @@ export default function CellView() {
async function loadCellData() {
try {
// TODO: handle first row > 100kb
const df = await parquetDataFrame(`/api/store/get?key=${key}`)
const df = await parquetDataFrame(url)
const rows = await df.rows(row, row + 1)
const cell = rows[0][col]
setText(stringify(cell))
Expand All @@ -64,11 +66,16 @@ export default function CellView() {
return <Layout error={error} title={key}>
<nav className='top-header'>
<div className='path'>
<a href='/files'>/</a>
{key && key.split('/').slice(0, -1).map((sub, depth) =>
<a href={`/files/${path.slice(2, depth + 3).join('/')}/`} key={depth}>{sub}/</a>
)}
<a href={`/files/${key}`}>{path.at(-1)}</a>
{isUrl &&
<a href={`/files?key=${key}`}>{key}</a>
}
{!isUrl && <>
<a href='/files'>/</a>
{key && key.split('/').slice(0, -1).map((sub, depth) =>
<a href={`/files?key=${path.slice(0, depth + 1).join('/')}/`} key={depth}>{sub}/</a>
)}
<a href={`/files?key=${key}`}>{path.at(-1)}</a>
</>}
</div>
</nav>

Expand Down
47 changes: 24 additions & 23 deletions src/components/File.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,47 @@ export default function File() {
const [dataframe, setDataframe] = useState<DataFrame>()

// File path from url
const path = location.pathname.split('/')
const key = decodeURI(path.slice(2).join('/'))
const search = new URLSearchParams(location.search)
const key = decodeURIComponent(search.get('key') || '').replace(/\/$/, '')
const path = key.split('/')

if (!key.endsWith('.parquet')) {
return <Layout error={new Error('Invalid file type')} title={key}>
<div className='center'>Invalid file type</div>
</Layout>
}
const isUrl = key.startsWith('http://') || key.startsWith('https://')
const url = isUrl ? key : '/api/store/get?key=' + key

// Filename loaded immediately from url, file contents loaded async
const [loading, setLoading] = useState(false)

useEffect(() => {
parquetDataFrame('/api/store/get?key=' + key)
parquetDataFrame(url)
.then(setDataframe)
.catch(setError)
.finally(() => setLoading(false))
}, [])

function onDoubleClickCell(row: number, col: number) {
location.href = '/files/' + key + '?row=' + row + '&col=' + col
location.href = '/files?key=' + key + '&row=' + row + '&col=' + col
}

return (
<Layout error={error} title={key}>
<nav className='top-header'>
<div className='path'>
return <Layout error={error} title={key}>
<nav className='top-header'>
<div className='path'>
{isUrl &&
<a href={`/files?key=${key}`}>{key}</a>
}
{!isUrl && <>
<a href='/files'>/</a>
{key && key.split('/').slice(0, -1).map((sub, depth) =>
<a href={`/files/${path.slice(2, depth + 3).join('/')}/`} key={depth}>{sub}/</a>
<a href={`/files?key=${path.slice(0, depth + 1).join('/')}/`} key={depth}>{sub}/</a>
)}
<a href={`/files/${key}`}>{path.at(-1)}</a>
</div>
</nav>
<a href={`/files?key=${key}`}>{path.at(-1)}</a>
</>}
</div>
</nav>

{dataframe &&
<HighTable data={dataframe} onDoubleClickCell={onDoubleClickCell} />
}
{dataframe &&
<HighTable data={dataframe} onDoubleClickCell={onDoubleClickCell} />
}

{loading && <Spinner className='center' />}
</Layout>
)
{loading && <Spinner className='center' />}
</Layout>
}
77 changes: 39 additions & 38 deletions src/components/Folder.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useEffect, useRef, useState } from 'react'
import { FileMetadata, getFileDate, getFileDateShort, getFileSize, listFiles } from '../files.js'
import {
FileMetadata, getFileDate, getFileDateShort, getFileSize, listFiles,
} from '../files.js'
import Layout, { Spinner, cn } from './Layout.js'

/**
Expand All @@ -12,55 +14,54 @@ export default function Folder() {
const listRef = useRef<HTMLUListElement>(null)

// Folder path from url
const path = location.pathname.split('/')
const prefix = decodeURI(path.slice(2, -1).join('/'))
const search = new URLSearchParams(location.search)
const key = (search.get('key') || '').replace(/\/$/, '')
const path = key.split('/')

// Fetch files on component mount
useEffect(() => {
listFiles(prefix)
listFiles(key)
.then(setFiles)
.catch(error => {
setFiles([])
setError(error)
})
}, [prefix])
}, [key])

function fileUrl(file: FileMetadata): string {
return prefix ? `/files/${prefix}/${file.key}` : `/files/${file.key}`
return key ? `/files?key=${key}/${file.key}` : `/files?key=${file.key}`
}

return (
<Layout error={error} title={prefix}>
<nav className='top-header'>
<div className='path'>
<a href='/files'>/</a>
{prefix && prefix.split('/').map((sub, depth) =>
<a href={`/files/${path.slice(2, depth + 3).join('/')}/`} key={depth}>{sub}/</a>
)}
</div>
</nav>
return <Layout error={error} title={key}>
<nav className='top-header'>
<div className='path'>
<a href='/files'>/</a>
{key && key.split('/').map((sub, depth) =>
<a href={`/files?key=${path.slice(0, depth + 1).join('/')}/`} key={depth}>{sub}/</a>
)}
</div>
</nav>

{files && files.length > 0 && <ul className='file-list' ref={listRef}>
{files.map((file, index) =>
<li key={index}>
<a href={fileUrl(file)}>
<span className={cn('file-name', 'file', file.key.endsWith('/') && 'folder')}>
{file.key}
{files && files.length > 0 && <ul className='file-list' ref={listRef}>
{files.map((file, index) =>
<li key={index}>
<a href={fileUrl(file)}>
<span className={cn('file-name', 'file', file.key.endsWith('/') && 'folder')}>
{file.key}
</span>
{!file.key.endsWith('/') && <>
<span className='file-size' title={file.fileSize?.toLocaleString() + ' bytes'}>
{getFileSize(file)}
</span>
{!file.key.endsWith('/') && <>
<span className='file-size' title={file.fileSize?.toLocaleString() + ' bytes'}>
{getFileSize(file)}
</span>
<span className='file-date' title={getFileDate(file)}>
{getFileDateShort(file)}
</span>
</>}
</a>
</li>
)}
</ul>}
{files?.length === 0 && <div className='center'>No files</div>}
{files === undefined && <Spinner className='center' />}
</Layout>
)
<span className='file-date' title={getFileDate(file)}>
{getFileDateShort(file)}
</span>
</>}
</a>
</li>
)}
</ul>}
{files?.length === 0 && <div className='center'>No files</div>}
{files === undefined && <Spinner className='center' />}
</Layout>
}
57 changes: 0 additions & 57 deletions src/components/Url.tsx

This file was deleted.

15 changes: 5 additions & 10 deletions src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,23 @@ import ReactDOM from 'react-dom'
import Cell from './components/Cell.js'
import File from './components/File.js'
import Folder from './components/Folder.js'
import Url from './components/Url.js'

function render() {
const app = document.getElementById('app')
if (!app) throw new Error('missing app element')
console.log('rendering', location.pathname)

// @ts-expect-error TODO: fix react createRoot type
const root = ReactDOM.createRoot(app)
if (location.pathname.endsWith('/')) {
const search = new URLSearchParams(location.search)
if (!search.has('key') || search.get('key')?.endsWith('/')) {
// folder view
root.render(React.createElement(Folder))
} else if (location.pathname.startsWith('/url/')) {
// url view
root.render(React.createElement(Url))
} else if (location.search) {
// local cell view
} else if (search.has('row') && search.has('col')) {
// cell view
root.render(React.createElement(Cell))
} else {
// local file view
// file view
root.render(React.createElement(File))
}

}
render()
13 changes: 7 additions & 6 deletions src/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,11 @@ function handleRequest(req) {
.replace('file://', '')
.replace('/src/serve.js', '')

if (pathname === '/' || pathname === '/files') {
if (pathname === '/' || pathname === '/files/') {
// redirect to /files
return { status: 301, content: '/files/' }
} else if (pathname.startsWith('/files/') || pathname.startsWith('/url/')) {
return { status: 301, content: '/files' }
} else if (pathname.startsWith('/files')) {
// serve index.html
console.log('serving index.html', `${hyperparamPath}/public/index.html`)
return handleStatic(`${hyperparamPath}/public/index.html`)
} else if (pathname.startsWith('/public/')) {
// serve static files
Expand Down Expand Up @@ -245,8 +244,10 @@ export function serve({ port = 2048, path = '' }) {
}).listen(port, () => {
console.log(`hyperparam server running on http://localhost:${port}`)
if (!path) openUrl(`http://localhost:${port}`)
else if (path.startsWith('http')) openUrl(`http://localhost:${port}/url/${path}`)
else openUrl(`http://localhost:${port}/files/${path}`)
else {
path = encodeURIComponent(path)
openUrl(`http://localhost:${port}/files?key=${path}`)
}
})
}

Expand Down

0 comments on commit b54659c

Please sign in to comment.