@@ -203,6 +203,60 @@ const formatBytes = (bytes: number, decimals = 2) => {
203203 return `${ parseFloat ( ( bytes / Math . pow ( k , i ) ) . toFixed ( dm ) ) } ${ sizes [ i ] } ` ;
204204} ;
205205
206+ // Lazy loading thumbnail component
207+ import { getDownloadURL , ref } from "firebase/storage" ;
208+ import { storage } from "@/lib/firebase" ;
209+
210+ const AssetThumbnail = ( { asset } : { asset : Asset } ) => {
211+ const [ url , setUrl ] = useState < string | null > ( asset . url || null ) ;
212+ const [ loading , setLoading ] = useState ( ! asset . url ) ;
213+ const [ error , setError ] = useState ( false ) ;
214+
215+ useEffect ( ( ) => {
216+ if ( url ) return ;
217+
218+ let active = true ;
219+ const fetchUrl = async ( ) => {
220+ try {
221+ const downloadUrl = await getDownloadURL ( ref ( storage , asset . path ) ) ;
222+ if ( active ) {
223+ setUrl ( downloadUrl ) ;
224+ setLoading ( false ) ;
225+ }
226+ } catch ( e ) {
227+ if ( active ) {
228+ console . error ( "Failed to load thumbnail" , e ) ;
229+ setError ( true ) ;
230+ setLoading ( false ) ;
231+ }
232+ }
233+ } ;
234+
235+ fetchUrl ( ) ;
236+ return ( ) => {
237+ active = false ;
238+ } ;
239+ } , [ asset . path , url ] ) ;
240+
241+ if ( loading )
242+ return < div className = "animate-pulse bg-white/10 w-full h-full" /> ;
243+ if ( error || ! url )
244+ return (
245+ < div className = "flex items-center justify-center w-full h-full bg-white/5" >
246+ < ImageIcon className = "h-8 w-8 text-white/20" />
247+ </ div >
248+ ) ;
249+
250+ return (
251+ < img
252+ src = { url }
253+ alt = { asset . name }
254+ className = "object-cover w-full h-full transition-transform group-hover:scale-105"
255+ onError = { ( ) => setError ( true ) }
256+ />
257+ ) ;
258+ } ;
259+
206260export function AssetManager ( { rootPath = "" } : { rootPath ?: string } ) {
207261 // Root level management for Tabs
208262 const {
@@ -246,6 +300,7 @@ export function AssetManager({ rootPath = "" }: { rootPath?: string }) {
246300 getFileBlob,
247301 downloadFolder,
248302 downloadFile,
303+ syncIndex,
249304 } = useAssets ( currentPath ) ;
250305
251306 const [ newFolderName , setNewFolderName ] = useState ( "" ) ;
@@ -633,11 +688,7 @@ export function AssetManager({ rootPath = "" }: { rootPath?: string }) {
633688 </ div >
634689 < div className = "aspect-square bg-black/40 flex items-center justify-center relative overflow-hidden" >
635690 { asset . mimeType ?. startsWith ( "image/" ) ? (
636- < img
637- src = { asset . url }
638- alt = { asset . name }
639- className = "object-cover w-full h-full transition-transform group-hover:scale-105"
640- />
691+ < AssetThumbnail asset = { asset } />
641692 ) : (
642693 ( ( ) => {
643694 const { icon : Icon , color } = getFileIcon (
@@ -689,8 +740,11 @@ export function AssetManager({ rootPath = "" }: { rootPath?: string }) {
689740 >
690741 { asset . name }
691742 </ p >
692- < p className = "text-xs text-white/40 mt-1" >
693- { formatBytes ( asset . size || 0 ) }
743+ < p
744+ className = "text-xs text-white/40 mt-1 truncate"
745+ title = { searchTerm ? asset . path : undefined }
746+ >
747+ { searchTerm ? asset . path : formatBytes ( asset . size || 0 ) }
694748 </ p >
695749 </ div >
696750 < DropdownMenu >
@@ -978,15 +1032,26 @@ export function AssetManager({ rootPath = "" }: { rootPath?: string }) {
9781032
9791033 { /* Search and Actions Row */ }
9801034 < div className = "flex flex-col sm:flex-row gap-2" >
981- < div className = "relative flex-1" >
982- < Search className = "absolute left-2.5 top-2.5 h-4 w-4 text-white/50" />
983- < Input
984- type = "search"
985- placeholder = "Search assets..."
986- className = "pl-8 bg-[#121212] border-white/20 focus-visible:ring-purple-500 w-full"
987- value = { searchTerm }
988- onChange = { ( e ) => setSearchTerm ( e . target . value ) }
989- />
1035+ < div className = "flex gap-2 w-full sm:w-auto" >
1036+ { /* Manual Re-Index Button Removed as per user request */ }
1037+
1038+ < div className = "flex-1 sm:flex-none relative" >
1039+ < Search className = "absolute left-2.5 top-2.5 h-4 w-4 text-white/40" />
1040+ < Input
1041+ className = "pl-9 w-full sm:w-[250px] bg-white/5 border-white/10 text-white placeholder:text-white/40 focus:border-purple-500/50"
1042+ placeholder = "Search assets..."
1043+ value = { searchTerm }
1044+ onChange = { ( e ) => setSearchTerm ( e . target . value ) }
1045+ />
1046+ { searchTerm && (
1047+ < button
1048+ onClick = { ( ) => setSearchTerm ( "" ) }
1049+ className = "absolute right-2.5 top-2.5 text-white/40 hover:text-white"
1050+ >
1051+ < X className = "h-4 w-4" />
1052+ </ button >
1053+ ) }
1054+ </ div >
9901055 </ div >
9911056
9921057 < div className = "flex gap-2" >
0 commit comments