forge
This commit is contained in:
parent
b1060dbdce
commit
6f13ba0f66
|
|
@ -19,8 +19,9 @@
|
|||
"typescript": "^5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@because/forge": "*",
|
||||
"@because/howl": "*",
|
||||
"@because/hype": "*",
|
||||
"@because/toes": "*",
|
||||
"@because/howl": "*"
|
||||
"@because/toes": "*"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"typescript": "^5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@because/forge": "*",
|
||||
"@because/hype": "*",
|
||||
"@because/toes": "*"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"typescript": "^5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@because/forge": "*",
|
||||
"@because/hype": "*",
|
||||
"@because/toes": "*"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user