1- import { memo } from 'react'
1+ import { memo , useState } from 'react'
22import { Button } from '@/components/ui/button'
3- import { Download as DownloadIcon , Loader as LoaderIcon } from 'lucide-react'
3+ import { Popover , PopoverContent , PopoverTrigger } from '@/components/ui/popover'
4+ import { Download as DownloadIcon , Loader as LoaderIcon , ChevronDown } from 'lucide-react'
45import { useRepoStore } from '@/stores/repoStore'
56import { useImageCount } from '@/hooks/features/filter/useImageCount'
67import { useImageDownload } from '@/hooks/features/download/useImageDownload'
8+ import type { FlattenMode } from '@/utils'
9+ import { cn } from '@/utils'
710
811interface ImageCountBadgeProps {
912 filteredCount : number
@@ -29,52 +32,109 @@ const ImageCountBadge = memo(
2932 } ,
3033)
3134
35+ const flattenModeLabels : Record < FlattenMode , string > = {
36+ original : 'Original paths' ,
37+ 'last-level' : 'Last level only' ,
38+ flat : 'Flat (filename only)' ,
39+ }
40+
3241const DownloadButton = memo ( function DownloadButton ( ) {
3342 const repo = useRepoStore ( state => state . repo )
3443 const filteredImageFiles = useRepoStore ( state => state . filteredImageFiles )
3544 const { filteredCount} = useImageCount ( )
45+ const [ flattenMode , setFlattenMode ] = useState < FlattenMode > ( 'original' )
46+ const [ popoverOpen , setPopoverOpen ] = useState ( false )
3647
3748 const { isDownloading, downloadProgress, handleDownload} = useImageDownload ( {
3849 repo,
3950 imagePaths : filteredImageFiles || [ ] ,
51+ flattenMode,
4052 } )
4153
54+ const handleDownloadClick = ( ) => {
55+ setPopoverOpen ( false )
56+ handleDownload ( )
57+ }
58+
4259 return (
43- < Button
44- aria-label = "Download all filtered images as ZIP"
45- disabled = { isDownloading || ! filteredCount }
46- onClick = { handleDownload }
47- size = "sm"
48- variant = "outline"
49- className = "text-xs font-semibold flex flex-col items-center gap-0.5 min-w-[190px]" >
50- { isDownloading ? (
51- < >
52- < div className = "flex items-center gap-1" >
53- < LoaderIcon className = "size-4 animate-spin" />
54- < span >
55- { downloadProgress !== null
56- ? `DOWNLOADING ${ downloadProgress } %`
57- : 'DOWNLOADING...' }
58- </ span >
59- </ div >
60- { downloadProgress !== null && (
61- < div
62- className = "mt-0.5 h-1 w-full rounded-full bg-muted overflow-hidden"
63- aria-hidden = "true" >
64- < div
65- className = "h-full bg-accent transition-[width] duration-150 ease-out"
66- style = { { width : `${ downloadProgress } %` } }
67- />
60+ < Popover open = { popoverOpen } onOpenChange = { setPopoverOpen } >
61+ < div className = "flex gap-1" >
62+ < Button
63+ aria-label = "Download all filtered images as ZIP"
64+ disabled = { isDownloading || ! filteredCount }
65+ onClick = { handleDownloadClick }
66+ size = "sm"
67+ variant = "outline"
68+ className = "text-xs font-semibold flex flex-col items-center gap-0.5 min-w-[160px]" >
69+ { isDownloading ? (
70+ < >
71+ < div className = "flex items-center gap-1" >
72+ < LoaderIcon className = "size-4 animate-spin" />
73+ < span >
74+ { downloadProgress !== null
75+ ? `DOWNLOADING ${ downloadProgress } %`
76+ : 'DOWNLOADING...' }
77+ </ span >
78+ </ div >
79+ { downloadProgress !== null && (
80+ < div
81+ className = "mt-0.5 h-1 w-full rounded-full bg-muted overflow-hidden"
82+ aria-hidden = "true" >
83+ < div
84+ className = "h-full bg-accent transition-[width] duration-150 ease-out"
85+ style = { { width : `${ downloadProgress } %` } }
86+ />
87+ </ div >
88+ ) }
89+ </ >
90+ ) : (
91+ < div className = "flex items-center gap-1" >
92+ < DownloadIcon className = "size-4" />
93+ < span > DOWNLOAD FILTERED</ span >
6894 </ div >
6995 ) }
70- </ >
71- ) : (
72- < div className = "flex items-center gap-1" >
73- < DownloadIcon className = "size-4" />
74- < span > DOWNLOAD FILTERED</ span >
96+ </ Button >
97+ { ! isDownloading && (
98+ < PopoverTrigger asChild >
99+ < Button
100+ aria-label = "Download options"
101+ size = "sm"
102+ variant = "outline"
103+ disabled = { ! filteredCount }
104+ className = "px-2" >
105+ < ChevronDown className = "size-4" />
106+ </ Button >
107+ </ PopoverTrigger >
108+ ) }
109+ </ div >
110+ < PopoverContent side = "bottom" align = "end" className = "w-64" >
111+ < div className = "space-y-2" >
112+ < p className = "text-xs font-semibold text-foreground" > Path structure</ p >
113+ < div className = "space-y-1" >
114+ { ( [ 'original' , 'last-level' , 'flat' ] as FlattenMode [ ] ) . map ( mode => (
115+ < button
116+ key = { mode }
117+ type = "button"
118+ onClick = { ( ) => {
119+ setFlattenMode ( mode )
120+ setPopoverOpen ( false )
121+ } }
122+ className = { cn (
123+ 'w-full text-left px-2 py-1.5 rounded text-xs transition-colors' ,
124+ flattenMode === mode
125+ ? 'bg-accent text-accent-foreground'
126+ : 'hover:bg-muted text-muted-foreground' ,
127+ ) } >
128+ { flattenModeLabels [ mode ] }
129+ </ button >
130+ ) ) }
131+ </ div >
132+ < p className = "text-xs text-muted-foreground pt-1 border-t" >
133+ Duplicate names will be renamed with -1, -2, etc.
134+ </ p >
75135 </ div >
76- ) }
77- </ Button >
136+ </ PopoverContent >
137+ </ Popover >
78138 )
79139} )
80140
0 commit comments