forge
This commit is contained in:
parent
b1060dbdce
commit
6f13ba0f66
|
|
@ -19,8 +19,9 @@
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@because/forge": "*",
|
||||||
|
"@because/howl": "*",
|
||||||
"@because/hype": "*",
|
"@because/hype": "*",
|
||||||
"@because/toes": "*",
|
"@because/toes": "*"
|
||||||
"@because/howl": "*"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Hype } from '@because/hype'
|
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 { readdir, stat } from 'fs/promises'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { join, extname, basename } from 'path'
|
import { join, extname, basename } from 'path'
|
||||||
|
|
@ -12,29 +13,12 @@ const app = new Hype({ prettyHTML: false })
|
||||||
const Container = define('Container', {
|
const Container = define('Container', {
|
||||||
fontFamily: theme('fonts-sans'),
|
fontFamily: theme('fonts-sans'),
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
|
paddingTop: 0,
|
||||||
maxWidth: '1200px',
|
maxWidth: '1200px',
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
color: theme('colors-text'),
|
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', {
|
const FileList = define('FileList', {
|
||||||
listStyle: 'none',
|
listStyle: 'none',
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
|
@ -114,14 +98,19 @@ const ErrorBox = define('ErrorBox', {
|
||||||
margin: '20px 0',
|
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',
|
base: 'a',
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
color: theme('colors-link'),
|
color: theme('colors-link'),
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '5px',
|
|
||||||
marginBottom: '15px',
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
textDecoration: 'underline',
|
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 = () => (
|
const FolderIcon = () => (
|
||||||
<FileIcon viewBox="0 0 24 24">
|
<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" />
|
<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 {
|
interface LayoutProps {
|
||||||
title: string
|
title: string
|
||||||
subtitle?: string
|
|
||||||
children: Child
|
children: Child
|
||||||
highlight?: boolean
|
highlight?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function Layout({ title, subtitle, children, highlight }: LayoutProps) {
|
function Layout({ title, children, highlight }: LayoutProps) {
|
||||||
return (
|
return (
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<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 {
|
function getLanguage(filename: string): string {
|
||||||
const ext = extname(filename).toLowerCase()
|
const ext = extname(filename).toLowerCase()
|
||||||
const langMap: Record<string, string> = {
|
const langMap: Record<string, string> = {
|
||||||
|
|
@ -229,7 +263,6 @@ app.get('/', async c => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const appPath = join(APPS_DIR, appName, version)
|
const appPath = join(APPS_DIR, appName, version)
|
||||||
const versionSuffix = version !== 'current' ? ` @ ${version}` : ''
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await stat(appPath)
|
await stat(appPath)
|
||||||
|
|
@ -248,23 +281,21 @@ app.get('/', async c => {
|
||||||
fileStats = await stat(fullPath)
|
fileStats = await stat(fullPath)
|
||||||
} catch {
|
} catch {
|
||||||
return c.html(
|
return c.html(
|
||||||
<Layout title="Code Browser" subtitle={appName + versionSuffix}>
|
<Layout title="Code Browser">
|
||||||
<ErrorBox>Path "{filePath}" not found</ErrorBox>
|
<ErrorBox>Path "{filePath}" not found</ErrorBox>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentPath = filePath.split('/').slice(0, -1).join('/')
|
|
||||||
const versionParam = version !== 'current' ? `&version=${version}` : ''
|
const versionParam = version !== 'current' ? `&version=${version}` : ''
|
||||||
const backUrl = `/?app=${appName}${versionParam}${parentPath ? `&file=${parentPath}` : ''}`
|
|
||||||
|
|
||||||
if (fileStats.isFile()) {
|
if (fileStats.isFile()) {
|
||||||
const content = readFileSync(fullPath, 'utf-8')
|
const content = readFileSync(fullPath, 'utf-8')
|
||||||
const language = getLanguage(basename(fullPath))
|
const language = getLanguage(basename(fullPath))
|
||||||
|
|
||||||
return c.html(
|
return c.html(
|
||||||
<Layout title={`${appName}/${filePath}`} subtitle={`${appName}${versionSuffix}/${filePath}`} highlight>
|
<Layout title={`${appName}/${filePath}`} highlight>
|
||||||
<BackLink href={backUrl}>← Back</BackLink>
|
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
||||||
<CodeBlock>
|
<CodeBlock>
|
||||||
<CodeHeader>{basename(fullPath)}</CodeHeader>
|
<CodeHeader>{basename(fullPath)}</CodeHeader>
|
||||||
<pre><code class={`language-${language}`}>{content}</code></pre>
|
<pre><code class={`language-${language}`}>{content}</code></pre>
|
||||||
|
|
@ -276,8 +307,8 @@ app.get('/', async c => {
|
||||||
const files = await listFiles(appPath, filePath)
|
const files = await listFiles(appPath, filePath)
|
||||||
|
|
||||||
return c.html(
|
return c.html(
|
||||||
<Layout title={`${appName}${filePath ? `/${filePath}` : ''}`} subtitle={`${appName}${versionSuffix}${filePath ? `/${filePath}` : ''}`}>
|
<Layout title={`${appName}${filePath ? `/${filePath}` : ''}`}>
|
||||||
{filePath && <BackLink href={backUrl}>← Back</BackLink>}
|
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
||||||
<FileList>
|
<FileList>
|
||||||
{files.map(file => (
|
{files.map(file => (
|
||||||
<FileItem>
|
<FileItem>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@because/forge": "*",
|
||||||
"@because/hype": "*",
|
"@because/hype": "*",
|
||||||
"@because/toes": "*"
|
"@because/toes": "*"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Hype } from '@because/hype'
|
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 { readFileSync, writeFileSync, existsSync } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Hype } from '@because/hype'
|
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 { readdir, readlink, stat } from 'fs/promises'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import type { Child } from 'hono/jsx'
|
import type { Child } from 'hono/jsx'
|
||||||
|
|
@ -11,6 +12,7 @@ const app = new Hype({ prettyHTML: false })
|
||||||
const Container = define('Container', {
|
const Container = define('Container', {
|
||||||
fontFamily: theme('fonts-sans'),
|
fontFamily: theme('fonts-sans'),
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
|
paddingTop: 0,
|
||||||
maxWidth: '800px',
|
maxWidth: '800px',
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
color: theme('colors-text'),
|
color: theme('colors-text'),
|
||||||
|
|
@ -110,7 +112,6 @@ function Layout({ title, subtitle, children }: LayoutProps) {
|
||||||
<Container>
|
<Container>
|
||||||
<Header>
|
<Header>
|
||||||
<Title>Versions</Title>
|
<Title>Versions</Title>
|
||||||
{subtitle && <Subtitle>{subtitle}</Subtitle>}
|
|
||||||
</Header>
|
</Header>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
@ -129,7 +130,7 @@ async function getVersions(appPath: string): Promise<{ name: string; isCurrent:
|
||||||
let currentTarget = ''
|
let currentTarget = ''
|
||||||
try {
|
try {
|
||||||
currentTarget = await readlink(join(appPath, 'current'))
|
currentTarget = await readlink(join(appPath, 'current'))
|
||||||
} catch {}
|
} catch { }
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
.filter(e => e.isDirectory() && /^\d{8}-\d{6}$/.test(e.name))
|
.filter(e => e.isDirectory() && /^\d{8}-\d{6}$/.test(e.name))
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@because/forge": "*",
|
||||||
"@because/hype": "*",
|
"@because/hype": "*",
|
||||||
"@because/toes": "*"
|
"@because/toes": "*"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,6 @@ export function AppDetail({ app, render }: { app: App, render: () => void }) {
|
||||||
return (
|
return (
|
||||||
<TabContent key={tool.name} active={isSelected}>
|
<TabContent key={tool.name} active={isSelected}>
|
||||||
<Section>
|
<Section>
|
||||||
<SectionTitle>{toolName}</SectionTitle>
|
|
||||||
{tool.state !== 'running' && (
|
{tool.state !== 'running' && (
|
||||||
<p style={{ color: theme('colors-textFaint') }}>
|
<p style={{ color: theme('colors-textFaint') }}>
|
||||||
Tool is {stateLabels[tool.state].toLowerCase()}
|
Tool is {stateLabels[tool.state].toLowerCase()}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user