fix things
This commit is contained in:
parent
4cbe5c2566
commit
d1b7e973d3
|
|
@ -5,6 +5,7 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "code",
|
"name": "code",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@because/forge": "*",
|
||||||
"@because/howl": "*",
|
"@because/howl": "*",
|
||||||
"@because/hype": "*",
|
"@because/hype": "*",
|
||||||
"@because/toes": "*",
|
"@because/toes": "*",
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,69 @@ const BreadcrumbCurrent = define('BreadcrumbCurrent', {
|
||||||
fontWeight: 500,
|
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 = () => (
|
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" />
|
||||||
|
|
@ -145,6 +208,28 @@ interface LayoutProps {
|
||||||
highlight?: boolean
|
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) {
|
function Layout({ title, children, highlight }: LayoutProps) {
|
||||||
return (
|
return (
|
||||||
<html>
|
<html>
|
||||||
|
|
@ -162,6 +247,7 @@ function Layout({ title, children, highlight }: LayoutProps) {
|
||||||
)}
|
)}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: fileMemoryScript }} />
|
||||||
<script dangerouslySetInnerHTML={{ __html: initScript }} />
|
<script dangerouslySetInnerHTML={{ __html: initScript }} />
|
||||||
<Container>
|
<Container>
|
||||||
{children}
|
{children}
|
||||||
|
|
@ -176,6 +262,25 @@ app.get('/styles.css', c => c.text(baseStyles + stylesToCSS(), 200, {
|
||||||
'Content-Type': 'text/css; charset=utf-8',
|
'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 = '') {
|
async function listFiles(appPath: string, subPath: string = '') {
|
||||||
const fullPath = join(appPath, subPath)
|
const fullPath = join(appPath, subPath)
|
||||||
const entries = await readdir(fullPath, { withFileTypes: true })
|
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 {
|
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> = {
|
||||||
|
|
@ -249,6 +370,7 @@ function getLanguage(filename: string): string {
|
||||||
return langMap[ext] || 'plaintext'
|
return langMap[ext] || 'plaintext'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
app.get('/', async c => {
|
app.get('/', async c => {
|
||||||
const appName = c.req.query('app')
|
const appName = c.req.query('app')
|
||||||
const version = c.req.query('version') || 'current'
|
const version = c.req.query('version') || 'current'
|
||||||
|
|
@ -290,14 +412,75 @@ app.get('/', async c => {
|
||||||
const versionParam = version !== 'current' ? `&version=${version}` : ''
|
const versionParam = version !== 'current' ? `&version=${version}` : ''
|
||||||
|
|
||||||
if (fileStats.isFile()) {
|
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 content = readFileSync(fullPath, 'utf-8')
|
||||||
const language = getLanguage(basename(fullPath))
|
const language = getLanguage(filename)
|
||||||
|
|
||||||
return c.html(
|
return c.html(
|
||||||
<Layout title={`${appName}/${filePath}`} highlight>
|
<Layout title={`${appName}/${filePath}`} highlight>
|
||||||
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
||||||
<CodeBlock>
|
<CodeBlock>
|
||||||
<CodeHeader>{basename(fullPath)}</CodeHeader>
|
<CodeHeader>{filename}</CodeHeader>
|
||||||
<pre><code class={`language-${language}`}>{content}</code></pre>
|
<pre><code class={`language-${language}`}>{content}</code></pre>
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "todo",
|
"name": "todo",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@because/forge": "*",
|
||||||
"@because/hype": "*",
|
"@because/hype": "*",
|
||||||
"@because/toes": "*",
|
"@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",
|
"name": "versions",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@because/forge": "*",
|
||||||
"@because/hype": "*",
|
"@because/hype": "*",
|
||||||
"@because/toes": "*",
|
"@because/toes": "*",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export let sidebarSection: 'apps' | 'tools' = (localStorage.getItem('sidebarSect
|
||||||
export let apps: App[] = []
|
export let apps: App[] = []
|
||||||
|
|
||||||
// Tab state
|
// Tab state
|
||||||
export let selectedTab: string = 'overview'
|
export let selectedTab: string = localStorage.getItem('selectedTab') || 'overview'
|
||||||
|
|
||||||
// State setters
|
// State setters
|
||||||
export function setSelectedApp(name: string | null) {
|
export function setSelectedApp(name: string | null) {
|
||||||
|
|
@ -37,4 +37,5 @@ export function setApps(newApps: App[]) {
|
||||||
|
|
||||||
export function setSelectedTab(tab: string) {
|
export function setSelectedTab(tab: string) {
|
||||||
selectedTab = tab
|
selectedTab = tab
|
||||||
|
localStorage.setItem('selectedTab', tab)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,7 @@ app.route('/api/sync', syncRouter)
|
||||||
console.log('🐾 Toes!')
|
console.log('🐾 Toes!')
|
||||||
initApps()
|
initApps()
|
||||||
|
|
||||||
export default app.defaults
|
export default {
|
||||||
|
...app.defaults,
|
||||||
|
maxRequestBodySize: 1024 * 1024 * 50, // 50MB
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user