This commit is contained in:
Chris Wanstrath 2026-01-30 19:55:21 -08:00
parent b1060dbdce
commit 6f13ba0f66
7 changed files with 76 additions and 41 deletions

View File

@ -19,8 +19,9 @@
"typescript": "^5.9.2"
},
"dependencies": {
"@because/forge": "*",
"@because/howl": "*",
"@because/hype": "*",
"@because/toes": "*",
"@because/howl": "*"
"@because/toes": "*"
}
}

View File

@ -1,5 +1,6 @@
import { Hype } from '@because/hype'
import { theme, define, stylesToCSS, initScript, baseStyles } from '@because/toes/tools'
import { define, stylesToCSS } from '@because/forge'
import { baseStyles, initScript, theme } from '@because/toes/tools'
import { readdir, stat } from 'fs/promises'
import { readFileSync } from 'fs'
import { join, extname, basename } from 'path'
@ -12,29 +13,12 @@ const app = new Hype({ prettyHTML: false })
const Container = define('Container', {
fontFamily: theme('fonts-sans'),
padding: '20px',
paddingTop: 0,
maxWidth: '1200px',
margin: '0 auto',
color: theme('colors-text'),
})
const Header = define('Header', {
marginBottom: '20px',
paddingBottom: '10px',
borderBottom: `2px solid ${theme('colors-border')}`,
})
const Title = define('Title', {
margin: 0,
fontSize: '24px',
fontWeight: 'bold',
})
const Subtitle = define('Subtitle', {
color: theme('colors-textMuted'),
fontSize: '18px',
marginTop: '5px',
})
const FileList = define('FileList', {
listStyle: 'none',
padding: 0,
@ -114,14 +98,19 @@ const ErrorBox = define('ErrorBox', {
margin: '20px 0',
})
const BackLink = define('BackLink', {
const Breadcrumb = define('Breadcrumb', {
display: 'flex',
alignItems: 'center',
gap: '6px',
fontSize: '14px',
marginBottom: '15px',
flexWrap: 'wrap',
})
const BreadcrumbLink = define('BreadcrumbLink', {
base: 'a',
textDecoration: 'none',
color: theme('colors-link'),
display: 'inline-flex',
alignItems: 'center',
gap: '5px',
marginBottom: '15px',
states: {
':hover': {
textDecoration: 'underline',
@ -129,6 +118,15 @@ const BackLink = define('BackLink', {
},
})
const BreadcrumbSeparator = define('BreadcrumbSeparator', {
color: theme('colors-textMuted'),
})
const BreadcrumbCurrent = define('BreadcrumbCurrent', {
color: theme('colors-text'),
fontWeight: 500,
})
const FolderIcon = () => (
<FileIcon viewBox="0 0 24 24">
<path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z" />
@ -143,12 +141,11 @@ const FileIconSvg = () => (
interface LayoutProps {
title: string
subtitle?: string
children: Child
highlight?: boolean
}
function Layout({ title, subtitle, children, highlight }: LayoutProps) {
function Layout({ title, children, highlight }: LayoutProps) {
return (
<html>
<head>
@ -197,6 +194,43 @@ async function listFiles(appPath: string, subPath: string = '') {
})
}
interface BreadcrumbProps {
appName: string
filePath: string
versionParam: string
}
function PathBreadcrumb({ appName, filePath, versionParam }: BreadcrumbProps) {
const parts = filePath ? filePath.split('/').filter(Boolean) : []
const crumbs: { name: string; path: string }[] = []
let currentPath = ''
for (const part of parts) {
currentPath = currentPath ? `${currentPath}/${part}` : part
crumbs.push({ name: part, path: currentPath })
}
return (
<Breadcrumb>
{crumbs.length > 0 ? (
<BreadcrumbLink href={`/?app=${appName}${versionParam}`}>{appName}</BreadcrumbLink>
) : (
<BreadcrumbCurrent>{appName}</BreadcrumbCurrent>
)}
{crumbs.map((crumb, i) => (
<>
<BreadcrumbSeparator>/</BreadcrumbSeparator>
{i === crumbs.length - 1 ? (
<BreadcrumbCurrent>{crumb.name}</BreadcrumbCurrent>
) : (
<BreadcrumbLink href={`/?app=${appName}${versionParam}&file=${crumb.path}`}>{crumb.name}</BreadcrumbLink>
)}
</>
))}
</Breadcrumb>
)
}
function getLanguage(filename: string): string {
const ext = extname(filename).toLowerCase()
const langMap: Record<string, string> = {
@ -229,7 +263,6 @@ app.get('/', async c => {
}
const appPath = join(APPS_DIR, appName, version)
const versionSuffix = version !== 'current' ? ` @ ${version}` : ''
try {
await stat(appPath)
@ -248,23 +281,21 @@ app.get('/', async c => {
fileStats = await stat(fullPath)
} catch {
return c.html(
<Layout title="Code Browser" subtitle={appName + versionSuffix}>
<Layout title="Code Browser">
<ErrorBox>Path "{filePath}" not found</ErrorBox>
</Layout>
)
}
const parentPath = filePath.split('/').slice(0, -1).join('/')
const versionParam = version !== 'current' ? `&version=${version}` : ''
const backUrl = `/?app=${appName}${versionParam}${parentPath ? `&file=${parentPath}` : ''}`
if (fileStats.isFile()) {
const content = readFileSync(fullPath, 'utf-8')
const language = getLanguage(basename(fullPath))
return c.html(
<Layout title={`${appName}/${filePath}`} subtitle={`${appName}${versionSuffix}/${filePath}`} highlight>
<BackLink href={backUrl}> Back</BackLink>
<Layout title={`${appName}/${filePath}`} highlight>
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
<CodeBlock>
<CodeHeader>{basename(fullPath)}</CodeHeader>
<pre><code class={`language-${language}`}>{content}</code></pre>
@ -276,8 +307,8 @@ app.get('/', async c => {
const files = await listFiles(appPath, filePath)
return c.html(
<Layout title={`${appName}${filePath ? `/${filePath}` : ''}`} subtitle={`${appName}${versionSuffix}${filePath ? `/${filePath}` : ''}`}>
{filePath && <BackLink href={backUrl}> Back</BackLink>}
<Layout title={`${appName}${filePath ? `/${filePath}` : ''}`}>
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
<FileList>
{files.map(file => (
<FileItem>

View File

@ -19,6 +19,7 @@
"typescript": "^5.9.2"
},
"dependencies": {
"@because/forge": "*",
"@because/hype": "*",
"@because/toes": "*"
}

View File

@ -1,5 +1,6 @@
import { Hype } from '@because/hype'
import { theme, define, stylesToCSS, initScript, baseStyles } from '@because/toes/tools'
import { define, stylesToCSS } from '@because/forge'
import { baseStyles, initScript, theme } from '@because/toes/tools'
import { readFileSync, writeFileSync, existsSync } from 'fs'
import { join } from 'path'

View File

@ -1,5 +1,6 @@
import { Hype } from '@because/hype'
import { theme, define, stylesToCSS, initScript, baseStyles } from '@because/toes/tools'
import { define, stylesToCSS } from '@because/forge'
import { baseStyles, initScript, theme } from '@because/toes/tools'
import { readdir, readlink, stat } from 'fs/promises'
import { join } from 'path'
import type { Child } from 'hono/jsx'
@ -11,6 +12,7 @@ const app = new Hype({ prettyHTML: false })
const Container = define('Container', {
fontFamily: theme('fonts-sans'),
padding: '20px',
paddingTop: 0,
maxWidth: '800px',
margin: '0 auto',
color: theme('colors-text'),
@ -110,7 +112,6 @@ function Layout({ title, subtitle, children }: LayoutProps) {
<Container>
<Header>
<Title>Versions</Title>
{subtitle && <Subtitle>{subtitle}</Subtitle>}
</Header>
{children}
</Container>
@ -129,7 +130,7 @@ async function getVersions(appPath: string): Promise<{ name: string; isCurrent:
let currentTarget = ''
try {
currentTarget = await readlink(join(appPath, 'current'))
} catch {}
} catch { }
return entries
.filter(e => e.isDirectory() && /^\d{8}-\d{6}$/.test(e.name))

View File

@ -19,6 +19,7 @@
"typescript": "^5.9.2"
},
"dependencies": {
"@because/forge": "*",
"@because/hype": "*",
"@because/toes": "*"
}

View File

@ -151,7 +151,6 @@ export function AppDetail({ app, render }: { app: App, render: () => void }) {
return (
<TabContent key={tool.name} active={isSelected}>
<Section>
<SectionTitle>{toolName}</SectionTitle>
{tool.state !== 'running' && (
<p style={{ color: theme('colors-textFaint') }}>
Tool is {stateLabels[tool.state].toLowerCase()}