diff --git a/src/App.tsx b/src/App.tsx index f9dc817..a025e8f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Header } from './components/Header'; import { SearchBar } from './components/SearchBar'; import { PackageList } from './components/PackageList'; @@ -7,43 +7,57 @@ import { usePackages } from './hooks/usePackages'; export default function App() { const { packages, loading, error } = usePackages(); const [search, setSearch] = useState(''); + const [selectedRepository, setSelectedRepository] = useState<'core' | 'extra' | 'all'>('all'); + const [debouncedSearch, setDebouncedSearch] = useState(search); - // Filter packages based on search query - const filteredPackages = packages.filter((pkg) => - pkg.name.toLowerCase().includes(search.toLowerCase()) || - pkg.description.toLowerCase().includes(search.toLowerCase()) - ); + // Debounce search to optimize performance + useEffect(() => { + const timeoutId = setTimeout(() => { + setDebouncedSearch(search); + }, 300); // Wait for 300ms after the last keystroke + return () => clearTimeout(timeoutId); + }, [search]); + + // Filter packages based on search query and selected repository + const filteredPackages = packages + .filter((pkg) => { + // Filter by repository + if (selectedRepository !== 'all' && pkg.repository !== selectedRepository) { + return false; + } + // Filter by search query + return pkg.name.toLowerCase().includes(debouncedSearch.toLowerCase()) || + pkg.description.toLowerCase().includes(debouncedSearch.toLowerCase()); + }); + + const handleSearchChange = (value: string) => { + setSearch(value); + }; + + const handleRepositoryFilterChange = (repo: 'core' | 'extra' | 'all') => { + setSelectedRepository(repo); + }; return ( -
-
+
+
{/* Search Bar */}
- +
{/* Package Counter */}
-

- Showing {filteredPackages.length} package - {filteredPackages.length !== 1 ? 's' : ''} +

+ Showing {filteredPackages.length} package{filteredPackages.length !== 1 ? 's' : ''}

{/* Error State */} {error ? ( -
+

An error occurred while fetching packages: {error}

diff --git a/src/components/RepositorySelector.tsx b/src/components/RepositorySelector.tsx new file mode 100644 index 0000000..74867aa --- /dev/null +++ b/src/components/RepositorySelector.tsx @@ -0,0 +1,31 @@ +import { useState } from 'react'; + +type RepositorySelectorProps = { + onSelectRepository: (repo: 'core' | 'extra' | 'all') => void; +}; + +export function RepositorySelector({ onSelectRepository }: RepositorySelectorProps) { + const [selectedRepository, setSelectedRepository] = useState<'core' | 'extra' | 'all'>('all'); + + const handleChange = (event: React.ChangeEvent) => { + const selected = event.target.value as 'core' | 'extra' | 'all'; + setSelectedRepository(selected); + onSelectRepository(selected); // Pass the selection back to parent + }; + + return ( +
+ + +
+ ); +} diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index 89fcd1a..709aed2 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -3,9 +3,16 @@ import { Search } from 'lucide-react'; interface SearchBarProps { value: string; onChange: (value: string) => void; + selectedRepository: 'core' | 'extra' | 'all'; + onFilterChange: (repo: 'core' | 'extra' | 'all') => void; } -export function SearchBar({ value, onChange }: SearchBarProps) { +export function SearchBar({ + value, + onChange, + // selectedRepository, + // onFilterChange, +}: SearchBarProps) { return (
@@ -18,6 +25,17 @@ export function SearchBar({ value, onChange }: SearchBarProps) { placeholder="Search packages..." className="block w-full pl-10 pr-4 py-3 border-2 border-nord-4 dark:border-nord-2 rounded-xl bg-nord-5 dark:bg-nord-1 focus:ring-2 focus:ring-nord-8 focus:border-transparent text-nord-0 dark:text-nord-6 placeholder-nord-3 dark:placeholder-nord-4 transition-all ease-in-out duration-200 shadow-md hover:shadow-lg" /> + + {/* Dropdown for repository filter */} + {/* */}
); } diff --git a/src/components/fetchPackages.tsx b/src/components/fetchPackages.tsx new file mode 100644 index 0000000..b943238 --- /dev/null +++ b/src/components/fetchPackages.tsx @@ -0,0 +1,55 @@ +export interface Package { + name: string; + version: string; + description: string; + repository: 'core' | 'extra'; // This can be 'core', 'extra', or 'all' + } + + const MIRRORS = [ + 'https://raw.githubusercontent.com/Snigdha-OS/snigdhaos-core/refs/heads/master/packages.txt', + 'https://raw.githubusercontent.com/Snigdha-OS/snigdhaos-extra/refs/heads/master/packages.txt' + ]; + + async function fetchFromMirror(url: string): Promise { + const response = await fetch(url); + const text = await response.text(); + + // Determine the repository based on the URL + const repository = url.includes('snigdhaos-core') ? 'core' : 'extra'; + + return text + .split('\n') + .filter(Boolean) // Remove empty lines + .map((line) => { + const [name, version, ...descParts] = line.split(' '); + return { + name, + version, + description: descParts.join(' '), + repository, // Set the repository name dynamically + }; + }); + } + + export async function fetchPackages(): Promise { + let packages: Package[] = []; + + // Try fetching from each mirror + for (const mirror of MIRRORS) { + try { + const result = await fetchFromMirror(mirror); + packages = packages.concat(result); // Append fetched packages + } catch (error) { + console.warn(`Failed to fetch from mirror ${mirror}:`, error); + continue; // Continue to next mirror if one fails + } + } + + // If no packages were fetched, throw an error + if (packages.length === 0) { + throw new Error('All mirrors failed to respond'); + } + + return packages; + } + \ No newline at end of file diff --git a/src/services/api.ts b/src/services/api.ts index cf33335..a8fbda7 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -1,39 +1,43 @@ import { Package } from '../types'; +// Define the mirrors from which packages will be fetched const MIRRORS = [ 'https://raw.githubusercontent.com/Snigdha-OS/snigdhaos-core/refs/heads/master/packages.txt', 'https://raw.githubusercontent.com/Snigdha-OS/snigdhaos-extra/refs/heads/master/packages.txt' ]; +// Fetch data from a single mirror (Core or Extra repository) async function fetchFromMirror(url: string): Promise { const response = await fetch(url); const text = await response.text(); - - // determine the repository from the mirror URL (core or extra) + + // Determine the repository name (core or extra) from the URL const repository = url.includes('snigdhaos-core') ? 'core' : 'extra'; - + + // Parse the text file content and return a list of packages return text - .split('\n') - .filter(Boolean) + .split('\n') // Split by line + .filter(Boolean) // Remove empty lines .map((line) => { const [name, version, ...descParts] = line.split(' '); return { name, version, description: descParts.join(' '), - repository, // use the determined repository name + repository, // Attach the repository name to each package }; }); } +// Fetch packages from all mirrors and combine them export async function fetchPackages(): Promise { let packages: Package[] = []; - - // Try each mirror and accumulate results + + // Try fetching from each mirror and accumulate the results for (const mirror of MIRRORS) { try { const result = await fetchFromMirror(mirror); - packages = packages.concat(result); // Append to packages array + packages = packages.concat(result); // Append the packages from this mirror } catch (error) { console.warn(`Failed to fetch from mirror ${mirror}:`, error); continue; // Continue to the next mirror if this one fails @@ -44,6 +48,6 @@ export async function fetchPackages(): Promise { if (packages.length === 0) { throw new Error('All mirrors failed to respond'); } - + return packages; }