fix things
This commit is contained in:
parent
4cbe5c2566
commit
d1b7e973d3
|
|
@ -5,6 +5,7 @@
|
|||
"": {
|
||||
"name": "code",
|
||||
"dependencies": {
|
||||
"@because/forge": "*",
|
||||
"@because/howl": "*",
|
||||
"@because/hype": "*",
|
||||
"@because/toes": "*",
|
||||
|
|
|
|||
|
|
@ -127,6 +127,69 @@ const BreadcrumbCurrent = define('BreadcrumbCurrent', {
|
|||
fontWeight: 500,
|
||||
})
|
||||
|
||||
const MediaContainer = define('MediaContainer', {
|
||||
margin: '20px 0',
|
||||
border: `1px solid ${theme('colors-border')}`,
|
||||
borderRadius: theme('radius-md'),
|
||||
overflow: 'hidden',
|
||||
backgroundColor: theme('colors-bgSubtle'),
|
||||
})
|
||||
|
||||
const MediaHeader = define('MediaHeader', {
|
||||
padding: '10px 15px',
|
||||
backgroundColor: theme('colors-bgElement'),
|
||||
borderBottom: `1px solid ${theme('colors-border')}`,
|
||||
fontWeight: 'bold',
|
||||
fontSize: '14px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
})
|
||||
|
||||
const MediaContent = define('MediaContent', {
|
||||
padding: '20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
})
|
||||
|
||||
const ImagePreview = define('ImagePreview', {
|
||||
base: 'img',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '600px',
|
||||
objectFit: 'contain',
|
||||
})
|
||||
|
||||
const AudioPlayer = define('AudioPlayer', {
|
||||
base: 'audio',
|
||||
width: '100%',
|
||||
maxWidth: '500px',
|
||||
})
|
||||
|
||||
const VideoPlayer = define('VideoPlayer', {
|
||||
base: 'video',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '600px',
|
||||
})
|
||||
|
||||
const DownloadButton = define('DownloadButton', {
|
||||
base: 'a',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
padding: '8px 16px',
|
||||
backgroundColor: theme('colors-primary'),
|
||||
color: 'white',
|
||||
textDecoration: 'none',
|
||||
borderRadius: theme('radius-md'),
|
||||
fontSize: '14px',
|
||||
states: {
|
||||
':hover': {
|
||||
opacity: 0.9,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
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" />
|
||||
|
|
@ -145,6 +208,28 @@ interface LayoutProps {
|
|||
highlight?: boolean
|
||||
}
|
||||
|
||||
const fileMemoryScript = `
|
||||
(function() {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var app = params.get('app');
|
||||
var file = params.get('file');
|
||||
var version = params.get('version') || 'current';
|
||||
if (!app) return;
|
||||
var key = 'code-app:' + app + ':' + version + ':file';
|
||||
if (file) {
|
||||
localStorage.setItem(key, file);
|
||||
} else {
|
||||
var saved = localStorage.getItem(key);
|
||||
if (saved) {
|
||||
var url = '/?app=' + encodeURIComponent(app);
|
||||
if (version !== 'current') url += '&version=' + encodeURIComponent(version);
|
||||
url += '&file=' + encodeURIComponent(saved);
|
||||
window.location.replace(url);
|
||||
}
|
||||
}
|
||||
})();
|
||||
`
|
||||
|
||||
function Layout({ title, children, highlight }: LayoutProps) {
|
||||
return (
|
||||
<html>
|
||||
|
|
@ -162,6 +247,7 @@ function Layout({ title, children, highlight }: LayoutProps) {
|
|||
)}
|
||||
</head>
|
||||
<body>
|
||||
<script dangerouslySetInnerHTML={{ __html: fileMemoryScript }} />
|
||||
<script dangerouslySetInnerHTML={{ __html: initScript }} />
|
||||
<Container>
|
||||
{children}
|
||||
|
|
@ -176,6 +262,25 @@ app.get('/styles.css', c => c.text(baseStyles + stylesToCSS(), 200, {
|
|||
'Content-Type': 'text/css; charset=utf-8',
|
||||
}))
|
||||
|
||||
app.get('/raw', async c => {
|
||||
const appName = c.req.query('app')
|
||||
const version = c.req.query('version') || 'current'
|
||||
const filePath = c.req.query('file')
|
||||
|
||||
if (!appName || !filePath) {
|
||||
return c.text('Missing app or file parameter', 400)
|
||||
}
|
||||
|
||||
const fullPath = join(APPS_DIR, appName, version, filePath)
|
||||
const file = Bun.file(fullPath)
|
||||
|
||||
if (!await file.exists()) {
|
||||
return c.text(`File not found: ${fullPath}`, 404)
|
||||
}
|
||||
|
||||
return new Response(file)
|
||||
})
|
||||
|
||||
async function listFiles(appPath: string, subPath: string = '') {
|
||||
const fullPath = join(appPath, subPath)
|
||||
const entries = await readdir(fullPath, { withFileTypes: true })
|
||||
|
|
@ -231,6 +336,22 @@ function PathBreadcrumb({ appName, filePath, versionParam }: BreadcrumbProps) {
|
|||
)
|
||||
}
|
||||
|
||||
const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', '.bmp'])
|
||||
const AUDIO_EXTS = new Set(['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac'])
|
||||
const VIDEO_EXTS = new Set(['.mp4', '.webm', '.mov', '.avi'])
|
||||
const BINARY_EXTS = new Set(['.pdf', '.zip', '.tar', '.gz', '.exe', '.dmg', '.woff', '.woff2', '.ttf', '.otf', '.eot'])
|
||||
|
||||
type FileType = 'text' | 'image' | 'audio' | 'video' | 'binary'
|
||||
|
||||
function getFileType(filename: string): FileType {
|
||||
const ext = extname(filename).toLowerCase()
|
||||
if (IMAGE_EXTS.has(ext)) return 'image'
|
||||
if (AUDIO_EXTS.has(ext)) return 'audio'
|
||||
if (VIDEO_EXTS.has(ext)) return 'video'
|
||||
if (BINARY_EXTS.has(ext)) return 'binary'
|
||||
return 'text'
|
||||
}
|
||||
|
||||
function getLanguage(filename: string): string {
|
||||
const ext = extname(filename).toLowerCase()
|
||||
const langMap: Record<string, string> = {
|
||||
|
|
@ -249,6 +370,7 @@ function getLanguage(filename: string): string {
|
|||
return langMap[ext] || 'plaintext'
|
||||
}
|
||||
|
||||
|
||||
app.get('/', async c => {
|
||||
const appName = c.req.query('app')
|
||||
const version = c.req.query('version') || 'current'
|
||||
|
|
@ -290,14 +412,75 @@ app.get('/', async c => {
|
|||
const versionParam = version !== 'current' ? `&version=${version}` : ''
|
||||
|
||||
if (fileStats.isFile()) {
|
||||
const filename = basename(fullPath)
|
||||
const fileType = getFileType(filename)
|
||||
const rawUrl = `/raw?app=${appName}${versionParam}&file=${filePath}`
|
||||
const downloadUrl = `${rawUrl}&download=1`
|
||||
|
||||
if (fileType === 'image') {
|
||||
return c.html(
|
||||
<Layout title={`${appName}/${filePath}`}>
|
||||
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
||||
<MediaContainer>
|
||||
<MediaHeader>
|
||||
<span>{filename}</span>
|
||||
<DownloadButton href={downloadUrl}>Download</DownloadButton>
|
||||
</MediaHeader>
|
||||
<MediaContent>
|
||||
<ImagePreview src={rawUrl} alt={filename} />
|
||||
</MediaContent>
|
||||
</MediaContainer>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
if (fileType === 'audio') {
|
||||
return c.html(
|
||||
<Layout title={`${appName}/${filePath}`}>
|
||||
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
||||
<MediaContainer>
|
||||
<MediaHeader>
|
||||
<span>{filename}</span>
|
||||
<DownloadButton href={downloadUrl}>Download</DownloadButton>
|
||||
</MediaHeader>
|
||||
<MediaContent>
|
||||
<AudioPlayer controls src={rawUrl} />
|
||||
</MediaContent>
|
||||
</MediaContainer>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
if (fileType === 'video') {
|
||||
return c.html(
|
||||
<Layout title={`${appName}/${filePath}`}>
|
||||
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
||||
<MediaContainer>
|
||||
<MediaHeader>
|
||||
<span>{filename}</span>
|
||||
<DownloadButton href={downloadUrl}>Download</DownloadButton>
|
||||
</MediaHeader>
|
||||
<MediaContent>
|
||||
<VideoPlayer controls src={rawUrl} />
|
||||
</MediaContent>
|
||||
</MediaContainer>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
if (fileType === 'binary') {
|
||||
return c.redirect(downloadUrl)
|
||||
}
|
||||
|
||||
// Text file - show with syntax highlighting
|
||||
const content = readFileSync(fullPath, 'utf-8')
|
||||
const language = getLanguage(basename(fullPath))
|
||||
const language = getLanguage(filename)
|
||||
|
||||
return c.html(
|
||||
<Layout title={`${appName}/${filePath}`} highlight>
|
||||
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
||||
<CodeBlock>
|
||||
<CodeHeader>{basename(fullPath)}</CodeHeader>
|
||||
<CodeHeader>{filename}</CodeHeader>
|
||||
<pre><code class={`language-${language}`}>{content}</code></pre>
|
||||
</CodeBlock>
|
||||
</Layout>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"": {
|
||||
"name": "todo",
|
||||
"dependencies": {
|
||||
"@because/forge": "*",
|
||||
"@because/hype": "*",
|
||||
"@because/toes": "*",
|
||||
},
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
|
@ -5,6 +5,7 @@
|
|||
"": {
|
||||
"name": "versions",
|
||||
"dependencies": {
|
||||
"@because/forge": "*",
|
||||
"@because/hype": "*",
|
||||
"@because/toes": "*",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export let sidebarSection: 'apps' | 'tools' = (localStorage.getItem('sidebarSect
|
|||
export let apps: App[] = []
|
||||
|
||||
// Tab state
|
||||
export let selectedTab: string = 'overview'
|
||||
export let selectedTab: string = localStorage.getItem('selectedTab') || 'overview'
|
||||
|
||||
// State setters
|
||||
export function setSelectedApp(name: string | null) {
|
||||
|
|
@ -37,4 +37,5 @@ export function setApps(newApps: App[]) {
|
|||
|
||||
export function setSelectedTab(tab: string) {
|
||||
selectedTab = tab
|
||||
localStorage.setItem('selectedTab', tab)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,7 @@ app.route('/api/sync', syncRouter)
|
|||
console.log('🐾 Toes!')
|
||||
initApps()
|
||||
|
||||
export default app.defaults
|
||||
export default {
|
||||
...app.defaults,
|
||||
maxRequestBodySize: 1024 * 1024 * 50, // 50MB
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user