diff --git a/src/app/api/file/route.ts b/src/app/api/file/route.ts index 36d76a2..cb97190 100644 --- a/src/app/api/file/route.ts +++ b/src/app/api/file/route.ts @@ -23,18 +23,34 @@ const MIME_TYPES: Record = { '.zip': 'application/zip', '.dmg': 'application/x-apple-diskimage', '.gz': 'application/gzip', + '.tgz': 'application/gzip', + '.bz2': 'application/x-bzip2', + '.xz': 'application/x-xz', + '.zst': 'application/zstd', } function getMimeType(filePath: string): string { - // Special-case .tar.gz before checking the last extension - if (filePath.toLowerCase().endsWith('.tar.gz')) return 'application/gzip' + // Special-case multi-part extensions before checking the last extension + const lower = filePath.toLowerCase() + if (lower.endsWith('.tar.gz')) return 'application/gzip' + if (lower.endsWith('.tar.bz2')) return 'application/x-bzip2' + if (lower.endsWith('.tar.xz')) return 'application/x-xz' + if (lower.endsWith('.tar.zst')) return 'application/zstd' const ext = path.extname(filePath).toLowerCase() return MIME_TYPES[ext] ?? 'application/octet-stream' } function isDownloadAttachment(filePath: string): boolean { const lower = filePath.toLowerCase() - return lower.endsWith('.zip') || lower.endsWith('.tar.gz') || lower.endsWith('.dmg') + return ( + lower.endsWith('.zip') || + lower.endsWith('.tar.gz') || + lower.endsWith('.tar.bz2') || + lower.endsWith('.tar.xz') || + lower.endsWith('.tar.zst') || + lower.endsWith('.tgz') || + lower.endsWith('.dmg') + ) } export async function GET(request: NextRequest) { diff --git a/src/app/icons/android.svg b/src/app/icons/android.svg new file mode 100644 index 0000000..b413770 --- /dev/null +++ b/src/app/icons/android.svg @@ -0,0 +1,6 @@ + + + +android + + \ No newline at end of file diff --git a/src/app/icons/linux.svg b/src/app/icons/linux.svg new file mode 100644 index 0000000..2172e59 --- /dev/null +++ b/src/app/icons/linux.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/app/icons/mac.svg b/src/app/icons/mac.svg new file mode 100644 index 0000000..381f84a --- /dev/null +++ b/src/app/icons/mac.svg @@ -0,0 +1,19 @@ + + + + + apple [#173] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app/icons/windows.svg b/src/app/icons/windows.svg new file mode 100644 index 0000000..81dcb9d --- /dev/null +++ b/src/app/icons/windows.svg @@ -0,0 +1,19 @@ + + + + + windows [#174] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/games/GameDetailModal.tsx b/src/components/games/GameDetailModal.tsx index 8d29e7e..b9a8508 100644 --- a/src/components/games/GameDetailModal.tsx +++ b/src/components/games/GameDetailModal.tsx @@ -4,15 +4,25 @@ import { useEffect, useRef, useState, useCallback } from 'react' import type { Game, GameFile, GamePlatform } from '@/types' import TagSelector from '@/components/tags/TagSelector' +// Import SVG icons +import WindowsIcon from '@/app/icons/windows.svg' +import LinuxIcon from '@/app/icons/linux.svg' +import MacosIcon from '@/app/icons/mac.svg' +import AndroidIcon from '@/app/icons/android.svg' + +// Update the PLATFORM_LABELS to include android const PLATFORM_LABELS: Record = { windows: 'WIN', linux: 'LIN', macos: 'MAC', + android: 'AND', } + const PLATFORM_COLORS: Record = { - windows: '#0078d4', - linux: '#e95420', - macos: '#6e6e73', + windows: '#85c0ec', + linux: '#efd27b', + macos: '#b0b0b7', + android: '#9ee0ca', } interface Props { @@ -516,13 +526,24 @@ export default function GameDetailModal({ game, libraryId, onClose, onTagsChange // ─── Download Button ────────────────────────────────────────────────────────── +const PLATFORM_ICONS: Record = { + windows: (typeof WindowsIcon === 'string' ? WindowsIcon : (WindowsIcon as { src: string }).src), + linux: (typeof LinuxIcon === 'string' ? LinuxIcon : (LinuxIcon as { src: string }).src), + macos: (typeof MacosIcon === 'string' ? MacosIcon : (MacosIcon as { src: string }).src), + android: (typeof AndroidIcon === 'string' ? AndroidIcon : (AndroidIcon as { src: string }).src), +} + function PlatformPill({ platform }: { platform: GamePlatform }) { + const src = PLATFORM_ICONS[platform] + return ( - {PLATFORM_LABELS[platform]} + {/* eslint-disable-next-line @next/next/no-img-element */} + {src && } + {PLATFORM_LABELS[platform]} ) } @@ -568,8 +589,9 @@ function DownloadButton({ onMouseLeave={(e) => ((e.currentTarget as HTMLElement).style.backgroundColor = 'var(--accent)')} > - - Download {primary.filename} + {primary.filename} + + ) } @@ -587,8 +609,8 @@ function DownloadButton({ onMouseLeave={(e) => ((e.currentTarget as HTMLElement).style.backgroundColor = 'transparent')} > - {primary.filename} + {/* Divider */} @@ -624,8 +646,8 @@ function DownloadButton({ onMouseLeave={(e) => ((e.currentTarget as HTMLElement).style.backgroundColor = 'transparent')} > - {file.filename} + ))} diff --git a/src/components/games/GamesView.tsx b/src/components/games/GamesView.tsx index 66dc217..a278d1b 100644 --- a/src/components/games/GamesView.tsx +++ b/src/components/games/GamesView.tsx @@ -5,15 +5,37 @@ import type { Game, GamePlatform, GameSeries } from '@/types' import GameDetailModal from './GameDetailModal' import FilterPanel from '@/components/FilterPanel' +// Import SVG icons +import WindowsIcon from '@/app/icons/windows.svg' +import LinuxIcon from '@/app/icons/linux.svg' +import MacosIcon from '@/app/icons/mac.svg' +import AndroidIcon from '@/app/icons/android.svg' + const PLATFORM_LABELS: Record = { windows: 'WIN', linux: 'LIN', macos: 'MAC', + android: 'AND', } const PLATFORM_COLORS: Record = { - windows: '#0078d4', - linux: '#e95420', - macos: '#6e6e73', + windows: '#85c0ec', + linux: '#efd27b', + macos: '#b0b0b7', + android: '#9ee0ca', +} + +const PLATFORM_ICONS: Record = { + windows: (typeof WindowsIcon === 'string' ? WindowsIcon : (WindowsIcon as { src: string }).src), + linux: (typeof LinuxIcon === 'string' ? LinuxIcon : (LinuxIcon as { src: string }).src), + macos: (typeof MacosIcon === 'string' ? MacosIcon : (MacosIcon as { src: string }).src), + android: (typeof AndroidIcon === 'string' ? AndroidIcon : (AndroidIcon as { src: string }).src), +} + +function getPlatformIcon(platform: GamePlatform) { + const src = PLATFORM_ICONS[platform] + if (!src) return null + // eslint-disable-next-line @next/next/no-img-element + return } function PlatformBadges({ platforms }: { platforms: GamePlatform[] }) { @@ -23,10 +45,11 @@ function PlatformBadges({ platforms }: { platforms: GamePlatform[] }) { {platforms.map((p) => ( - {PLATFORM_LABELS[p]} + {getPlatformIcon(p)} + {PLATFORM_LABELS[p]} ))} diff --git a/src/lib/games.ts b/src/lib/games.ts index 8f4c151..30dbba7 100644 --- a/src/lib/games.ts +++ b/src/lib/games.ts @@ -11,7 +11,12 @@ function platformForFile(name: string): GamePlatform | null { const lower = name.toLowerCase() if (lower.endsWith('.zip')) return 'windows' if (lower.endsWith('.tar.gz')) return 'linux' + if (lower.endsWith('.tar.bz2')) return 'linux' + if (lower.endsWith('.tar.xz')) return 'linux' + if (lower.endsWith('.tar.zst')) return 'linux' + if (lower.endsWith('.tgz')) return 'linux' if (lower.endsWith('.dmg')) return 'macos' + if (lower.endsWith('.apk')) return 'android' return null } diff --git a/src/types/index.ts b/src/types/index.ts index 411375a..19d014c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -8,7 +8,7 @@ export interface Library { coverExt: string | null } -export type GamePlatform = 'windows' | 'linux' | 'macos' +export type GamePlatform = 'windows' | 'linux' | 'macos' | 'android' export interface GameFile { path: string