33import React , { useState , useRef , useEffect } from 'react' ;
44import { api } from '~/trpc/react' ;
55import { ScriptCard } from './ScriptCard' ;
6+ import { ScriptCardList } from './ScriptCardList' ;
67import { ScriptDetailModal } from './ScriptDetailModal' ;
78import { CategorySidebar } from './CategorySidebar' ;
89import { FilterBar , type FilterState } from './FilterBar' ;
10+ import { ViewToggle } from './ViewToggle' ;
911import { Button } from './ui/button' ;
1012import type { ScriptCard as ScriptCardType } from '~/types/script' ;
1113
@@ -22,6 +24,7 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
2224 const [ selectedSlug , setSelectedSlug ] = useState < string | null > ( null ) ;
2325 const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
2426 const [ selectedCategory , setSelectedCategory ] = useState < string | null > ( null ) ;
27+ const [ viewMode , setViewMode ] = useState < 'card' | 'list' > ( 'card' ) ;
2528 const [ filters , setFilters ] = useState < FilterState > ( {
2629 searchQuery : '' ,
2730 showUpdatable : null ,
@@ -40,7 +43,7 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
4043 { enabled : ! ! selectedSlug }
4144 ) ;
4245
43- // Load SAVE_FILTER setting and saved filters on component mount
46+ // Load SAVE_FILTER setting, saved filters, and view mode on component mount
4447 useEffect ( ( ) => {
4548 const loadSettings = async ( ) => {
4649 try {
@@ -63,6 +66,16 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
6366 }
6467 }
6568 }
69+
70+ // Load view mode
71+ const viewModeResponse = await fetch ( '/api/settings/view-mode' ) ;
72+ if ( viewModeResponse . ok ) {
73+ const viewModeData = await viewModeResponse . json ( ) ;
74+ const viewMode = viewModeData . viewMode ;
75+ if ( viewMode && typeof viewMode === 'string' && ( viewMode === 'card' || viewMode === 'list' ) ) {
76+ setViewMode ( viewMode ) ;
77+ }
78+ }
6679 } catch ( error ) {
6780 console . error ( 'Error loading settings:' , error ) ;
6881 } finally {
@@ -96,6 +109,29 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
96109 return ( ) => clearTimeout ( timeoutId ) ;
97110 } , [ filters , saveFiltersEnabled , isLoadingFilters ] ) ;
98111
112+ // Save view mode when it changes
113+ useEffect ( ( ) => {
114+ if ( isLoadingFilters ) return ;
115+
116+ const saveViewMode = async ( ) => {
117+ try {
118+ await fetch ( '/api/settings/view-mode' , {
119+ method : 'POST' ,
120+ headers : {
121+ 'Content-Type' : 'application/json' ,
122+ } ,
123+ body : JSON . stringify ( { viewMode } ) ,
124+ } ) ;
125+ } catch ( error ) {
126+ console . error ( 'Error saving view mode:' , error ) ;
127+ }
128+ } ;
129+
130+ // Debounce the save operation
131+ const timeoutId = setTimeout ( ( ) => void saveViewMode ( ) , 300 ) ;
132+ return ( ) => clearTimeout ( timeoutId ) ;
133+ } , [ viewMode , isLoadingFilters ] ) ;
134+
99135 // Extract categories from metadata
100136 const categories = React . useMemo ( ( ) : string [ ] => {
101137 if ( ! scriptCardsData ?. success || ! scriptCardsData . metadata ?. categories ) return [ ] ;
@@ -367,25 +403,8 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
367403
368404 return (
369405 < div className = "space-y-6" >
370- { /* Header with Stats */ }
371- < div className = "bg-card rounded-lg shadow p-6" >
372- < h2 className = "text-2xl font-bold text-foreground mb-4" > Downloaded Scripts</ h2 >
373-
374- < div className = "grid grid-cols-2 md:grid-cols-3 gap-4 mb-6" >
375- < div className = "bg-blue-500/10 border border-blue-500/20 p-4 rounded-lg" >
376- < div className = "text-2xl font-bold text-blue-400" > { downloadedScripts . length } </ div >
377- < div className = "text-sm text-blue-300" > Total Downloaded</ div >
378- </ div >
379- < div className = "bg-green-500/10 border border-green-500/20 p-4 rounded-lg" >
380- < div className = "text-2xl font-bold text-green-400" > { filterCounts . updatableCount } </ div >
381- < div className = "text-sm text-green-300" > Updatable</ div >
382- </ div >
383- < div className = "bg-purple-500/10 border border-purple-500/20 p-4 rounded-lg" >
384- < div className = "text-2xl font-bold text-purple-400" > { filteredScripts . length } </ div >
385- < div className = "text-sm text-purple-300" > Filtered Results</ div >
386- </ div >
387- </ div >
388- </ div >
406+
407+
389408
390409 < div className = "flex flex-col lg:flex-row gap-4 lg:gap-6" >
391410 { /* Category Sidebar */ }
@@ -412,6 +431,12 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
412431 isLoadingFilters = { isLoadingFilters }
413432 />
414433
434+ { /* View Toggle */ }
435+ < ViewToggle
436+ viewMode = { viewMode }
437+ onViewModeChange = { setViewMode }
438+ />
439+
415440 { /* Scripts Grid */ }
416441 { filteredScripts . length === 0 && ( filters . searchQuery || selectedCategory || filters . showUpdatable !== null || filters . selectedTypes . length > 0 ) ? (
417442 < div className = "text-center py-12" >
@@ -446,25 +471,47 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
446471 </ div >
447472 </ div >
448473 ) : (
449- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" >
450- { filteredScripts . map ( ( script , index ) => {
451- // Add validation to ensure script has required properties
452- if ( ! script || typeof script !== 'object' ) {
453- return null ;
454- }
455-
456- // Create a unique key by combining slug, name, and index to handle duplicates
457- const uniqueKey = `${ script . slug ?? 'unknown' } -${ script . name ?? 'unnamed' } -${ index } ` ;
458-
459- return (
460- < ScriptCard
461- key = { uniqueKey }
462- script = { script }
463- onClick = { handleCardClick }
464- />
465- ) ;
466- } ) }
467- </ div >
474+ viewMode === 'card' ? (
475+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" >
476+ { filteredScripts . map ( ( script , index ) => {
477+ // Add validation to ensure script has required properties
478+ if ( ! script || typeof script !== 'object' ) {
479+ return null ;
480+ }
481+
482+ // Create a unique key by combining slug, name, and index to handle duplicates
483+ const uniqueKey = `${ script . slug ?? 'unknown' } -${ script . name ?? 'unnamed' } -${ index } ` ;
484+
485+ return (
486+ < ScriptCard
487+ key = { uniqueKey }
488+ script = { script }
489+ onClick = { handleCardClick }
490+ />
491+ ) ;
492+ } ) }
493+ </ div >
494+ ) : (
495+ < div className = "space-y-3" >
496+ { filteredScripts . map ( ( script , index ) => {
497+ // Add validation to ensure script has required properties
498+ if ( ! script || typeof script !== 'object' ) {
499+ return null ;
500+ }
501+
502+ // Create a unique key by combining slug, name, and index to handle duplicates
503+ const uniqueKey = `${ script . slug ?? 'unknown' } -${ script . name ?? 'unnamed' } -${ index } ` ;
504+
505+ return (
506+ < ScriptCardList
507+ key = { uniqueKey }
508+ script = { script }
509+ onClick = { handleCardClick }
510+ />
511+ ) ;
512+ } ) }
513+ </ div >
514+ )
468515 ) }
469516
470517 < ScriptDetailModal
0 commit comments