Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import { ChevronRight, Folder, FileBox, ArrowUp, Home, Loader2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { breadcrumbs, formatBytes, isSupportedModel, parentPath, type FBItem } from '@/lib/filebrowser';
|
||||
|
||||
interface FolderBrowserProps {
|
||||
origin: string;
|
||||
path: string;
|
||||
items: FBItem[];
|
||||
loading?: boolean;
|
||||
selectedName: string | null;
|
||||
showAll: boolean;
|
||||
onToggleShowAll: (v: boolean) => void;
|
||||
onNavigate: (path: string) => void;
|
||||
onSelect: (name: string) => void;
|
||||
}
|
||||
|
||||
export function FolderBrowser({
|
||||
origin, path, items, loading, selectedName, showAll, onToggleShowAll, onNavigate, onSelect,
|
||||
}: FolderBrowserProps) {
|
||||
const crumbs = breadcrumbs(path);
|
||||
const visible = items.filter((it) => it.isDir || showAll || isSupportedModel(it));
|
||||
const dirs = visible.filter((i) => i.isDir).sort((a, b) => a.name.localeCompare(b.name));
|
||||
const files = visible.filter((i) => !i.isDir).sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{/* Breadcrumb */}
|
||||
<div className="flex items-center gap-1 text-[10px] font-mono text-muted-foreground bg-muted/30 px-2 py-1.5 rounded border border-border overflow-x-auto">
|
||||
<span className="opacity-60 truncate max-w-[120px]" title={origin}>{new URL(origin).hostname}</span>
|
||||
<ChevronRight className="h-3 w-3 shrink-0" />
|
||||
<button
|
||||
className="hover:text-primary flex items-center gap-1 shrink-0"
|
||||
onClick={() => onNavigate('/')}
|
||||
disabled={loading}
|
||||
>
|
||||
<Home className="h-3 w-3" /> root
|
||||
</button>
|
||||
{crumbs.map((c, i) => (
|
||||
<span key={c.path} className="flex items-center gap-1 shrink-0">
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
<button
|
||||
className={cn('hover:text-primary', i === crumbs.length - 1 && 'text-primary font-bold')}
|
||||
onClick={() => onNavigate(c.path)}
|
||||
disabled={loading}
|
||||
>
|
||||
{c.name}
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center justify-between text-[10px] font-mono">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 gap-1 text-[10px]"
|
||||
onClick={() => onNavigate(parentPath(path))}
|
||||
disabled={loading || path === '/'}
|
||||
>
|
||||
<ArrowUp className="h-3 w-3" /> Subir
|
||||
</Button>
|
||||
<label className="flex items-center gap-1.5 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showAll}
|
||||
onChange={(e) => onToggleShowAll(e.target.checked)}
|
||||
disabled={loading}
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
<span className="text-muted-foreground">Mostrar todos</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Listagem */}
|
||||
<ScrollArea className="h-64 border border-border rounded">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64 text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||||
<span className="text-xs font-mono">Carregando…</span>
|
||||
</div>
|
||||
) : visible.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-64 text-muted-foreground text-xs font-mono">
|
||||
Pasta vazia ou sem arquivos suportados
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-border">
|
||||
{dirs.map((it) => (
|
||||
<button
|
||||
key={`d-${it.name}`}
|
||||
onClick={() => onNavigate(`${path}${it.name}/`)}
|
||||
className="w-full flex items-center gap-2 px-2 py-1.5 hover:bg-muted/40 text-left text-xs font-mono"
|
||||
>
|
||||
<Folder className="h-3.5 w-3.5 text-primary shrink-0" />
|
||||
<span className="truncate flex-1">{it.name}</span>
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
</button>
|
||||
))}
|
||||
{files.map((it) => {
|
||||
const supported = isSupportedModel(it);
|
||||
const selected = selectedName === it.name;
|
||||
return (
|
||||
<button
|
||||
key={`f-${it.name}`}
|
||||
onClick={() => supported && onSelect(it.name)}
|
||||
disabled={!supported}
|
||||
className={cn(
|
||||
'w-full flex items-center gap-2 px-2 py-1.5 text-left text-xs font-mono',
|
||||
supported ? 'hover:bg-muted/40 cursor-pointer' : 'opacity-40 cursor-not-allowed',
|
||||
selected && 'bg-primary/15 ring-1 ring-primary',
|
||||
)}
|
||||
>
|
||||
<FileBox className={cn('h-3.5 w-3.5 shrink-0', supported ? 'text-accent' : 'text-muted-foreground')} />
|
||||
<span className="truncate flex-1">{it.name}</span>
|
||||
<span className="text-[10px] text-muted-foreground shrink-0">{formatBytes(it.size)}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user