-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
256 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
package-lock.json | ||
node_modules | ||
*.tgz | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import React, { useEffect, useRef, useState } from 'react' | ||
import { FileMetadata, getFileDate, getFileDateShort, getFileSize, listFiles } from './files.js' | ||
import Layout, { Spinner, cn } from './Layout.js' | ||
|
||
/** | ||
* Folder browser page | ||
*/ | ||
export default function Folder() { | ||
// State to hold file listing | ||
const [files, setFiles] = useState<FileMetadata[]>() | ||
const [error, setError] = useState<Error>() | ||
const listRef = useRef<HTMLUListElement>(null) | ||
|
||
// Folder path from url | ||
const path = location.pathname.split('/') | ||
let prefix = decodeURI(path.slice(2).join('/')) | ||
|
||
// Fetch files on component mount | ||
useEffect(() => { | ||
listFiles(prefix) | ||
.then(setFiles) | ||
.catch(error => { | ||
setFiles([]) | ||
setError(error) | ||
}) | ||
}, [prefix]) | ||
|
||
function fileUrl(file: FileMetadata): string { | ||
const key = prefix + '/' + file.key | ||
return file.key.endsWith('/') ? `/files/${key}` : `/files/${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> | ||
|
||
{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.key.endsWith('/') ? 'icon-directory' : 'icon-file')}> | ||
{file.key} | ||
</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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import React, { ReactNode } from 'react' | ||
|
||
interface LayoutProps { | ||
children: ReactNode | ||
className?: string | ||
error?: Error | ||
title?: string | ||
} | ||
|
||
/** | ||
* Layout for shared UI. | ||
* Content div style can be overridden by className prop. | ||
* | ||
* @param {Object} props | ||
* @param {ReactNode} props.children - content to display inside the layout | ||
* @param {string} props.className - additional class names to apply to the content container | ||
* @param {Error} props.error - error message to display | ||
* @param {string} props.title - page title | ||
* @returns rendered layout component | ||
*/ | ||
export default function Layout({ children, className, error, title }: LayoutProps) { | ||
const errorMessage = error?.toString() | ||
if (error) console.error(error) | ||
|
||
return <> | ||
<head> | ||
<title>{title ? `${title} - hyperparam` : 'hyperparam'}</title> | ||
<meta content="hyperparam is the missing UI for machine learning" name="description" /> | ||
<meta content="width=device-width, initial-scale=1" name="viewport" /> | ||
<link href="/favicon.png" rel="icon" /> | ||
</head> | ||
<main className='main'> | ||
<Sidebar /> | ||
<div className='content-container'> | ||
<div className={cn('content', className)}> | ||
{children} | ||
</div> | ||
<div className={cn('error-bar', error && 'show-error')}>{errorMessage}</div> | ||
</div> | ||
</main> | ||
</> | ||
} | ||
|
||
function Sidebar() { | ||
return ( | ||
<nav> | ||
<a className="brand" href='/'> | ||
<img | ||
alt="hyperparam" | ||
height={26} | ||
src="/assets/logo.svg" | ||
width={26} /> | ||
hyperparam | ||
</a> | ||
</nav> | ||
) | ||
} | ||
|
||
/** | ||
* Helper function to join class names | ||
*/ | ||
export function cn(...names: (string | undefined | false)[]): string { | ||
return names.filter(n => n).join(' ') | ||
} | ||
|
||
export function Spinner({ className }: { className: string }) { | ||
return <div className={cn('spinner', className)}></div> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
export interface FileMetadata { | ||
key: string | ||
eTag?: string | ||
fileSize?: number | ||
lastModified: string | ||
} | ||
|
||
export interface FileContent<T> { | ||
body: T | ||
key: string | ||
contentLength?: number | ||
contentType?: string | ||
eTag?: string | ||
fileName?: string | ||
fileSize?: number | ||
lastModified?: string | ||
contentRange?: string | ||
} | ||
|
||
/** | ||
* List user files from server | ||
* | ||
* @param prefix file path prefix | ||
*/ | ||
export async function listFiles(prefix: string, recursive?: boolean): Promise<FileMetadata[]> { | ||
const rec = recursive ? '&recursive=true' : '' | ||
prefix = encodeURIComponent(prefix) | ||
const res = await fetch(`/api/store/list?prefix=${prefix}${rec}`) | ||
if (res.ok) { | ||
return await res.json() | ||
} else { | ||
throw new Error(`file list error ${res.status} ${await res.text()}`) | ||
} | ||
} | ||
|
||
export function getFileDateShort(file?: { lastModified?: string }): string { | ||
const date = new Date(file?.lastModified!) | ||
// time if within last 24 hours, date otherwise | ||
const time = date.getTime() | ||
const now = Date.now() | ||
if (now - time < 86400000) { | ||
return date.toLocaleTimeString() | ||
} | ||
return date.toLocaleDateString() | ||
} | ||
|
||
/** | ||
* Parse date from lastModified field and format as locale string | ||
* | ||
* @param file file-like object with lastModified | ||
* @param file.lastModified last modified date string | ||
* @returns formatted date string | ||
*/ | ||
export function getFileDate(file?: { lastModified?: string }): string { | ||
const date = new Date(file?.lastModified!) | ||
return isFinite(date.getTime()) ? date.toLocaleString() : '' | ||
} | ||
|
||
/** | ||
* Format file size in human readable format | ||
* | ||
* @param file file-like object with fileSize | ||
* @param file.fileSize file size in bytes | ||
* @returns formatted file size string | ||
*/ | ||
export function getFileSize(file?: { fileSize?: number }): string { | ||
return file?.fileSize !== undefined ? formatFileSize(file?.fileSize) : '' | ||
} | ||
|
||
/** | ||
* Returns the file size in human readable format | ||
* | ||
* @param bytes file size in bytes | ||
* @returns formatted file size string | ||
*/ | ||
function formatFileSize(bytes: number): string { | ||
const sizes = ['b', 'kb', 'mb', 'gb', 'tb'] | ||
if (bytes === 0) return '0 b' | ||
const i = Math.floor(Math.log2(bytes) / 10) | ||
if (i === 0) return bytes + ' b' | ||
const base = bytes / Math.pow(1024, i) | ||
return (base < 10 ? base.toFixed(1) : Math.round(base)) + ' ' + sizes[i] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
{ | ||
"compilerOptions": { | ||
"jsx": "react", | ||
"allowJs": true, | ||
"checkJs": true, | ||
"lib": ["esnext", "dom"], | ||
|