2025-06-21 21:40:06 -03:00
|
|
|
"use client";
|
2025-06-29 11:58:23 -03:00
|
|
|
import React, { useState, useMemo, useEffect } from 'react';
|
2025-06-28 19:30:35 -03:00
|
|
|
import {
|
2025-06-28 22:36:36 -03:00
|
|
|
Search, Download, Trash2, Share, Star,
|
2025-06-29 11:58:23 -03:00
|
|
|
MoreVertical, Home, ChevronRight, ChevronLeft,
|
2025-06-28 19:30:35 -03:00
|
|
|
Folder, File, Image, Video, Music, FileText, Code, Database,
|
2025-06-28 22:36:36 -03:00
|
|
|
Clock, Users, Eye, Edit3, Copy, Scissors,
|
2025-06-29 11:58:23 -03:00
|
|
|
FolderPlus, Info, Lock, Menu,
|
|
|
|
ExternalLink, History, X
|
|
|
|
} from 'lucide-react';
|
2025-06-28 09:58:11 -03:00
|
|
|
import { cn } from "@/lib/utils";
|
2025-06-28 19:30:35 -03:00
|
|
|
import Footer from '../footer';
|
2025-06-29 11:58:23 -03:00
|
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
import { Separator } from '@/components/ui/separator';
|
|
|
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
|
|
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from '@/components/ui/context-menu';
|
|
|
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
|
|
|
import {
|
|
|
|
ResizableHandle,
|
|
|
|
ResizablePanel,
|
|
|
|
ResizablePanelGroup,
|
|
|
|
} from "@/components/ui/resizable";
|
|
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
|
|
import { Input } from '@/components/ui/input';
|
|
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
|
|
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
2025-06-28 19:30:35 -03:00
|
|
|
|
2025-06-28 09:58:11 -03:00
|
|
|
// Simple date formatting functions
|
|
|
|
const formatDate = (dateString) => {
|
|
|
|
const date = new Date(dateString);
|
2025-06-28 19:30:35 -03:00
|
|
|
return date.toLocaleDateString('en-US', {
|
|
|
|
month: 'short',
|
|
|
|
day: 'numeric',
|
|
|
|
year: 'numeric'
|
2025-06-28 09:58:11 -03:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const formatDateTime = (dateString) => {
|
|
|
|
const date = new Date(dateString);
|
|
|
|
return date.toLocaleString('en-US', {
|
|
|
|
month: 'short',
|
2025-06-28 19:30:35 -03:00
|
|
|
day: 'numeric',
|
2025-06-28 09:58:11 -03:00
|
|
|
year: 'numeric',
|
|
|
|
hour: 'numeric',
|
|
|
|
minute: '2-digit',
|
|
|
|
hour12: true
|
|
|
|
});
|
|
|
|
};
|
2025-06-21 21:40:06 -03:00
|
|
|
|
2025-06-28 09:58:11 -03:00
|
|
|
const formatDistanceToNow = (date) => {
|
|
|
|
const now = new Date();
|
2025-06-28 22:36:36 -03:00
|
|
|
const diffMs = now.getTime() - new Date(date).getTime();
|
2025-06-28 09:58:11 -03:00
|
|
|
const diffMinutes = Math.floor(diffMs / (1000 * 60));
|
|
|
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
|
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
2025-06-28 19:30:35 -03:00
|
|
|
|
2025-06-28 09:58:11 -03:00
|
|
|
if (diffMinutes < 1) return 'now';
|
|
|
|
if (diffMinutes < 60) return `${diffMinutes}m ago`;
|
|
|
|
if (diffHours < 24) return `${diffHours}h ago`;
|
|
|
|
if (diffDays < 7) return `${diffDays}d ago`;
|
|
|
|
return formatDate(date);
|
|
|
|
};
|
2025-06-28 19:30:35 -03:00
|
|
|
|
2025-06-28 09:58:11 -03:00
|
|
|
// File system data
|
2025-06-21 21:40:06 -03:00
|
|
|
const fileSystemData = {
|
|
|
|
"": {
|
2025-06-28 09:58:11 -03:00
|
|
|
id: "root",
|
|
|
|
name: "My Drive",
|
2025-06-21 21:40:06 -03:00
|
|
|
path: "",
|
|
|
|
is_dir: true,
|
2025-06-28 09:58:11 -03:00
|
|
|
children: ["projects", "documents", "media", "shared"]
|
2025-06-21 21:40:06 -03:00
|
|
|
},
|
|
|
|
"projects": {
|
2025-06-28 09:58:11 -03:00
|
|
|
id: "projects",
|
2025-06-21 21:40:06 -03:00
|
|
|
name: "Projects",
|
|
|
|
path: "projects",
|
|
|
|
is_dir: true,
|
|
|
|
modified: "2025-01-15T10:30:00Z",
|
|
|
|
starred: true,
|
|
|
|
shared: false,
|
2025-06-28 09:58:11 -03:00
|
|
|
children: ["web-apps", "mobile-apps", "ai-research"]
|
2025-06-21 21:40:06 -03:00
|
|
|
},
|
|
|
|
"projects/web-apps": {
|
2025-06-28 09:58:11 -03:00
|
|
|
id: "web-apps",
|
2025-06-21 21:40:06 -03:00
|
|
|
name: "Web Applications",
|
|
|
|
path: "projects/web-apps",
|
|
|
|
is_dir: true,
|
|
|
|
modified: "2025-01-14T16:45:00Z",
|
|
|
|
starred: false,
|
|
|
|
shared: true,
|
2025-06-28 09:58:11 -03:00
|
|
|
children: ["dashboard-pro", "package.json", "README.md"]
|
2025-06-21 21:40:06 -03:00
|
|
|
},
|
2025-06-28 09:58:11 -03:00
|
|
|
"projects/web-apps/package.json": {
|
|
|
|
id: "package-json",
|
2025-06-21 21:40:06 -03:00
|
|
|
name: "package.json",
|
2025-06-28 09:58:11 -03:00
|
|
|
path: "projects/web-apps/package.json",
|
2025-06-21 21:40:06 -03:00
|
|
|
is_dir: false,
|
|
|
|
size: 2048,
|
|
|
|
type: "json",
|
|
|
|
modified: "2025-01-13T14:20:00Z",
|
|
|
|
starred: false,
|
2025-06-28 09:58:11 -03:00
|
|
|
shared: false
|
2025-06-21 21:40:06 -03:00
|
|
|
},
|
2025-06-28 09:58:11 -03:00
|
|
|
"projects/web-apps/README.md": {
|
|
|
|
id: "readme-md",
|
2025-06-21 21:40:06 -03:00
|
|
|
name: "README.md",
|
2025-06-28 09:58:11 -03:00
|
|
|
path: "projects/web-apps/README.md",
|
2025-06-21 21:40:06 -03:00
|
|
|
is_dir: false,
|
|
|
|
size: 5120,
|
|
|
|
type: "markdown",
|
|
|
|
modified: "2025-01-12T09:30:00Z",
|
|
|
|
starred: false,
|
2025-06-28 09:58:11 -03:00
|
|
|
shared: true
|
2025-06-21 21:40:06 -03:00
|
|
|
},
|
|
|
|
"documents": {
|
2025-06-28 09:58:11 -03:00
|
|
|
id: "documents",
|
2025-06-21 21:40:06 -03:00
|
|
|
name: "Documents",
|
|
|
|
path: "documents",
|
|
|
|
is_dir: true,
|
|
|
|
modified: "2025-01-14T12:00:00Z",
|
|
|
|
starred: false,
|
|
|
|
shared: false,
|
2025-06-28 09:58:11 -03:00
|
|
|
children: ["proposals", "Q1-Strategy.pdf", "Budget-2025.xlsx"]
|
2025-06-21 21:40:06 -03:00
|
|
|
},
|
2025-06-28 09:58:11 -03:00
|
|
|
"documents/Q1-Strategy.pdf": {
|
|
|
|
id: "q1-strategy",
|
|
|
|
name: "Q1 Strategy.pdf",
|
|
|
|
path: "documents/Q1-Strategy.pdf",
|
2025-06-21 21:40:06 -03:00
|
|
|
is_dir: false,
|
|
|
|
size: 1048576,
|
|
|
|
type: "pdf",
|
|
|
|
modified: "2025-01-10T15:30:00Z",
|
|
|
|
starred: true,
|
2025-06-28 09:58:11 -03:00
|
|
|
shared: true
|
2025-06-21 21:40:06 -03:00
|
|
|
},
|
|
|
|
"media": {
|
2025-06-28 09:58:11 -03:00
|
|
|
id: "media",
|
2025-06-21 21:40:06 -03:00
|
|
|
name: "Media",
|
|
|
|
path: "media",
|
|
|
|
is_dir: true,
|
|
|
|
modified: "2025-01-13T18:45:00Z",
|
|
|
|
starred: false,
|
|
|
|
shared: false,
|
2025-06-28 09:58:11 -03:00
|
|
|
children: ["photos", "videos", "vacation-2024.jpg"]
|
2025-06-21 21:40:06 -03:00
|
|
|
},
|
2025-06-28 09:58:11 -03:00
|
|
|
"media/vacation-2024.jpg": {
|
|
|
|
id: "vacation-photo",
|
2025-06-21 21:40:06 -03:00
|
|
|
name: "vacation-2024.jpg",
|
2025-06-28 09:58:11 -03:00
|
|
|
path: "media/vacation-2024.jpg",
|
2025-06-21 21:40:06 -03:00
|
|
|
is_dir: false,
|
|
|
|
size: 3145728,
|
|
|
|
type: "image",
|
|
|
|
modified: "2024-12-25T20:00:00Z",
|
|
|
|
starred: true,
|
2025-06-28 09:58:11 -03:00
|
|
|
shared: false
|
2025-06-21 21:40:06 -03:00
|
|
|
},
|
|
|
|
"shared": {
|
2025-06-28 09:58:11 -03:00
|
|
|
id: "shared",
|
2025-06-21 21:40:06 -03:00
|
|
|
name: "Shared",
|
|
|
|
path: "shared",
|
|
|
|
is_dir: true,
|
|
|
|
modified: "2025-01-12T11:20:00Z",
|
|
|
|
starred: false,
|
|
|
|
shared: true,
|
2025-06-28 09:58:11 -03:00
|
|
|
children: ["team-resources", "client-files"]
|
2025-06-21 21:40:06 -03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const getFileIcon = (item) => {
|
|
|
|
if (item.is_dir) {
|
2025-06-29 11:58:23 -03:00
|
|
|
return <Folder className="w-4 h-4 text-yellow-500" />;
|
2025-06-21 21:40:06 -03:00
|
|
|
}
|
2025-06-28 19:30:35 -03:00
|
|
|
|
2025-06-21 21:40:06 -03:00
|
|
|
const iconMap = {
|
|
|
|
pdf: <FileText className="w-4 h-4 text-red-500" />,
|
2025-06-28 09:58:11 -03:00
|
|
|
xlsx: <Database className="w-4 h-4 text-green-600" />,
|
|
|
|
json: <Code className="w-4 h-4 text-yellow-600" />,
|
2025-06-21 21:40:06 -03:00
|
|
|
markdown: <Edit3 className="w-4 h-4 text-purple-500" />,
|
|
|
|
md: <Edit3 className="w-4 h-4 text-purple-500" />,
|
|
|
|
jpg: <Image className="w-4 h-4 text-pink-500" />,
|
|
|
|
jpeg: <Image className="w-4 h-4 text-pink-500" />,
|
|
|
|
png: <Image className="w-4 h-4 text-pink-500" />,
|
|
|
|
mp4: <Video className="w-4 h-4 text-red-600" />,
|
2025-06-28 09:58:11 -03:00
|
|
|
mp3: <Music className="w-4 h-4 text-green-600" />
|
2025-06-21 21:40:06 -03:00
|
|
|
};
|
2025-06-28 19:30:35 -03:00
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
return iconMap[item.type] || <File className="w-4 h-4 text-muted-foreground" />;
|
2025-06-21 21:40:06 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
const formatFileSize = (bytes) => {
|
|
|
|
if (!bytes) return '';
|
|
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
|
|
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
|
|
|
|
};
|
|
|
|
|
2025-06-28 19:30:35 -03:00
|
|
|
const FileContextMenu = ({ file, children, onAction }) => {
|
|
|
|
const contextMenuItems = [
|
|
|
|
{ icon: Eye, label: "Open", action: "open" },
|
|
|
|
{ icon: Download, label: "Download", action: "download" },
|
|
|
|
{ separator: true },
|
|
|
|
{
|
|
|
|
icon: Share, label: "Share", action: "share", submenu: [
|
|
|
|
{ icon: Users, label: "Share with team", action: "share-team" },
|
|
|
|
{ icon: ExternalLink, label: "Get link", action: "get-link" },
|
|
|
|
{ icon: Lock, label: "Restrict access", action: "restrict" },
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{ icon: Star, label: file?.starred ? "Remove from starred" : "Add to starred", action: "star" },
|
|
|
|
{ separator: true },
|
|
|
|
{ icon: Copy, label: "Copy", action: "copy" },
|
|
|
|
{ icon: Scissors, label: "Cut", action: "cut" },
|
|
|
|
{ icon: Edit3, label: "Rename", action: "rename" },
|
|
|
|
{ separator: true },
|
|
|
|
{
|
|
|
|
icon: FolderPlus, label: "Move to", action: "move", submenu: [
|
|
|
|
{ icon: Folder, label: "Documents", action: "move-documents" },
|
|
|
|
{ icon: Folder, label: "Projects", action: "move-projects" },
|
|
|
|
{ icon: Folder, label: "Choose folder...", action: "move-choose" },
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{ icon: Copy, label: "Make a copy", action: "duplicate" },
|
|
|
|
{ separator: true },
|
|
|
|
{ icon: History, label: "Version history", action: "history" },
|
|
|
|
{ icon: Info, label: "Details", action: "details" },
|
|
|
|
{ separator: true },
|
|
|
|
{ icon: Trash2, label: "Move to trash", action: "trash", destructive: true },
|
|
|
|
];
|
|
|
|
|
|
|
|
const renderContextMenuItem = (item, index) => {
|
|
|
|
if (item.separator) {
|
|
|
|
return <ContextMenuSeparator key={index} />;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item.submenu) {
|
|
|
|
return (
|
|
|
|
<ContextMenuSub key={index}>
|
|
|
|
<ContextMenuSubTrigger className="flex items-center gap-2">
|
|
|
|
<item.icon className="w-4 h-4" />
|
|
|
|
{item.label}
|
|
|
|
</ContextMenuSubTrigger>
|
2025-06-29 11:58:23 -03:00
|
|
|
<ContextMenuSubContent className="bg-popover">
|
2025-06-28 19:30:35 -03:00
|
|
|
{item.submenu.map((subItem, subIndex) => (
|
|
|
|
<ContextMenuItem
|
|
|
|
key={subIndex}
|
|
|
|
onClick={() => onAction(subItem.action, file)}
|
|
|
|
className="flex items-center gap-2"
|
|
|
|
>
|
|
|
|
<subItem.icon className="w-4 h-4" />
|
|
|
|
{subItem.label}
|
|
|
|
</ContextMenuItem>
|
|
|
|
))}
|
|
|
|
</ContextMenuSubContent>
|
|
|
|
</ContextMenuSub>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ContextMenuItem
|
|
|
|
key={index}
|
|
|
|
onClick={() => onAction(item.action, file)}
|
|
|
|
className={cn(
|
|
|
|
"flex items-center gap-2",
|
|
|
|
item.destructive && "text-destructive focus:text-destructive"
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<item.icon className="w-4 h-4" />
|
|
|
|
{item.label}
|
|
|
|
</ContextMenuItem>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ContextMenu>
|
|
|
|
<ContextMenuTrigger asChild>
|
|
|
|
{children}
|
|
|
|
</ContextMenuTrigger>
|
2025-06-29 11:58:23 -03:00
|
|
|
<ContextMenuContent className="w-56 bg-popover">
|
2025-06-28 19:30:35 -03:00
|
|
|
{contextMenuItems.map(renderContextMenuItem)}
|
|
|
|
</ContextMenuContent>
|
|
|
|
</ContextMenu>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
const FolderTree = ({ onSelect, selectedPath = undefined, isCollapsed = undefined, isMobile = undefined }) => {
|
2025-06-21 21:40:06 -03:00
|
|
|
const [expanded, setExpanded] = useState({ "": true, "projects": true });
|
|
|
|
|
|
|
|
const toggleExpand = (path) => {
|
|
|
|
setExpanded(prev => ({ ...prev, [path]: !prev[path] }));
|
|
|
|
onSelect(path);
|
|
|
|
};
|
|
|
|
|
2025-06-28 09:58:11 -03:00
|
|
|
const navLinks = [
|
|
|
|
{ title: "My Drive", path: "", icon: Home },
|
|
|
|
{ title: "Shared", path: "shared", icon: Users },
|
|
|
|
{ title: "Starred", path: "starred", icon: Star },
|
|
|
|
{ title: "Recent", path: "recent", icon: Clock },
|
|
|
|
{ title: "Trash", path: "trash", icon: Trash2 },
|
|
|
|
];
|
|
|
|
|
2025-06-21 21:40:06 -03:00
|
|
|
const renderTreeItem = (path, level = 0) => {
|
|
|
|
const item = fileSystemData[path];
|
|
|
|
if (!item || !item.is_dir) return null;
|
|
|
|
|
|
|
|
const isExpanded = expanded[path];
|
|
|
|
const isSelected = selectedPath === path;
|
|
|
|
|
|
|
|
return (
|
2025-06-28 09:58:11 -03:00
|
|
|
<div key={item.id}>
|
2025-06-28 19:30:35 -03:00
|
|
|
<button
|
2025-06-21 21:40:06 -03:00
|
|
|
onClick={() => toggleExpand(path)}
|
2025-06-28 09:58:11 -03:00
|
|
|
className={cn(
|
|
|
|
"flex items-center w-full px-2 py-1.5 text-sm rounded-md hover:bg-accent transition-colors",
|
2025-06-29 11:58:23 -03:00
|
|
|
isSelected && "bg-accent",
|
2025-06-28 09:58:11 -03:00
|
|
|
isCollapsed ? "justify-center" : "justify-start"
|
|
|
|
)}
|
2025-06-21 21:40:06 -03:00
|
|
|
>
|
2025-06-28 09:58:11 -03:00
|
|
|
{!isCollapsed && (
|
|
|
|
<>
|
|
|
|
<ChevronRight className={cn("w-4 h-4 mr-1 transition-transform", isExpanded && "rotate-90")} />
|
|
|
|
{getFileIcon(item)}
|
|
|
|
<span className="ml-2 truncate">{item.name}</span>
|
|
|
|
{item.starred && <Star className="w-3 h-3 ml-auto text-yellow-500 fill-current" />}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{isCollapsed && getFileIcon(item)}
|
|
|
|
</button>
|
|
|
|
{!isCollapsed && isExpanded && item.children && (
|
|
|
|
<div className="ml-4">
|
2025-06-21 21:40:06 -03:00
|
|
|
{item.children.map(childPath => {
|
|
|
|
const fullPath = path ? `${path}/${childPath}` : childPath;
|
|
|
|
return renderTreeItem(fullPath, level + 1);
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2025-06-29 11:58:23 -03:00
|
|
|
<div className={cn("flex flex-col h-full bg-background border-r border-border", isMobile ? "w-full" : "")}>
|
2025-06-28 09:58:11 -03:00
|
|
|
<div className={cn("p-2", isCollapsed ? "px-1" : "")}>
|
|
|
|
<nav className="space-y-1">
|
2025-06-28 19:30:35 -03:00
|
|
|
{navLinks.map((link) =>
|
2025-06-28 09:58:11 -03:00
|
|
|
isCollapsed ? (
|
|
|
|
<Tooltip key={link.path} delayDuration={0}>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button
|
|
|
|
variant={selectedPath === link.path ? "default" : "ghost"}
|
|
|
|
size="icon"
|
2025-06-29 11:58:23 -03:00
|
|
|
className="w-9 h-9 bg-background hover:bg-accent"
|
2025-06-28 09:58:11 -03:00
|
|
|
onClick={() => onSelect(link.path)}
|
|
|
|
>
|
|
|
|
<link.icon className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
2025-06-29 11:58:23 -03:00
|
|
|
<TooltipContent side="right" className="bg-popover">{link.title}</TooltipContent>
|
2025-06-28 09:58:11 -03:00
|
|
|
</Tooltip>
|
|
|
|
) : (
|
|
|
|
<Button
|
|
|
|
key={link.path}
|
|
|
|
variant={selectedPath === link.path ? "default" : "ghost"}
|
2025-06-29 11:58:23 -03:00
|
|
|
className="w-full justify-start bg-background hover:bg-accent"
|
2025-06-28 09:58:11 -03:00
|
|
|
onClick={() => onSelect(link.path)}
|
|
|
|
>
|
|
|
|
<link.icon className="mr-2 h-4 w-4" />
|
|
|
|
{link.title}
|
|
|
|
</Button>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
</nav>
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
2025-06-28 09:58:11 -03:00
|
|
|
{!isCollapsed && (
|
|
|
|
<>
|
2025-06-29 11:58:23 -03:00
|
|
|
<Separator className="bg-border" />
|
2025-06-28 09:58:11 -03:00
|
|
|
<ScrollArea className="flex-1 p-2">
|
|
|
|
{renderTreeItem("")}
|
|
|
|
</ScrollArea>
|
|
|
|
</>
|
|
|
|
)}
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
const FileList = ({ path, searchTerm, filterType, selectedFile, setSelectedFile, onContextAction, isMobile }) => {
|
2025-06-21 21:40:06 -03:00
|
|
|
const files = useMemo(() => {
|
2025-06-28 09:58:11 -03:00
|
|
|
const currentItem = fileSystemData[path];
|
2025-06-21 21:40:06 -03:00
|
|
|
if (!currentItem || !currentItem.is_dir || !currentItem.children) return [];
|
2025-06-28 19:30:35 -03:00
|
|
|
|
2025-06-21 21:40:06 -03:00
|
|
|
let items = currentItem.children.map(childName => {
|
|
|
|
const childPath = path ? `${path}/${childName}` : childName;
|
|
|
|
return fileSystemData[childPath];
|
|
|
|
}).filter(Boolean);
|
|
|
|
|
|
|
|
if (searchTerm) {
|
2025-06-28 19:30:35 -03:00
|
|
|
items = items.filter(item =>
|
2025-06-28 09:58:11 -03:00
|
|
|
item.name.toLowerCase().includes(searchTerm.toLowerCase())
|
2025-06-21 21:40:06 -03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filterType && filterType !== 'all') {
|
|
|
|
items = items.filter(item => {
|
|
|
|
if (filterType === 'folders') return item.is_dir;
|
|
|
|
if (filterType === 'files') return !item.is_dir;
|
|
|
|
if (filterType === 'starred') return item.starred;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-06-28 09:58:11 -03:00
|
|
|
return items.sort((a, b) => {
|
|
|
|
if (a.is_dir && !b.is_dir) return -1;
|
|
|
|
if (!a.is_dir && b.is_dir) return 1;
|
|
|
|
return a.name.localeCompare(b.name);
|
2025-06-21 21:40:06 -03:00
|
|
|
});
|
2025-06-28 09:58:11 -03:00
|
|
|
}, [path, searchTerm, filterType]);
|
2025-06-21 21:40:06 -03:00
|
|
|
|
2025-06-28 09:58:11 -03:00
|
|
|
return (
|
2025-06-29 11:58:23 -03:00
|
|
|
<ScrollArea className={cn("h-full bg-background", isMobile ? "w-full" : "")}>
|
2025-06-28 09:58:11 -03:00
|
|
|
<div className="flex flex-col">
|
2025-06-21 21:40:06 -03:00
|
|
|
{files.map((item) => (
|
2025-06-28 19:30:35 -03:00
|
|
|
<FileContextMenu
|
2025-06-28 09:58:11 -03:00
|
|
|
key={item.id}
|
2025-06-28 19:30:35 -03:00
|
|
|
file={item}
|
|
|
|
onAction={onContextAction}
|
2025-06-28 09:58:11 -03:00
|
|
|
>
|
2025-06-28 19:30:35 -03:00
|
|
|
<button
|
|
|
|
className={cn(
|
2025-06-29 11:58:23 -03:00
|
|
|
"flex items-center gap-3 p-3 text-left text-sm transition-all hover:bg-accent border-b border-border",
|
|
|
|
selectedFile?.id === item.id && "bg-accent"
|
2025-06-28 19:30:35 -03:00
|
|
|
)}
|
|
|
|
onClick={() => setSelectedFile(item)}
|
|
|
|
>
|
|
|
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
|
|
|
{getFileIcon(item)}
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
<div className="font-medium truncate">{item.name}</div>
|
|
|
|
{item.starred && <Star className="w-3 h-3 text-yellow-500 fill-current" />}
|
|
|
|
{item.shared && <Users className="w-3 h-3 text-blue-500" />}
|
|
|
|
</div>
|
|
|
|
<div className="text-xs text-muted-foreground">
|
|
|
|
{item.is_dir ? 'Folder' : formatFileSize(item.size)}
|
|
|
|
</div>
|
2025-06-28 09:58:11 -03:00
|
|
|
</div>
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
2025-06-28 19:30:35 -03:00
|
|
|
<div className="text-xs text-muted-foreground">
|
2025-06-28 22:36:36 -03:00
|
|
|
{formatDistanceToNow(new Date(item.modified))}
|
2025-06-28 19:30:35 -03:00
|
|
|
</div>
|
|
|
|
</button>
|
|
|
|
</FileContextMenu>
|
2025-06-21 21:40:06 -03:00
|
|
|
))}
|
|
|
|
</div>
|
2025-06-28 09:58:11 -03:00
|
|
|
</ScrollArea>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
const FileDisplay = ({ file, isMobile }) => {
|
2025-06-28 09:58:11 -03:00
|
|
|
if (!file) {
|
|
|
|
return (
|
2025-06-29 11:58:23 -03:00
|
|
|
<div className={cn("flex flex-col items-center justify-center h-full text-center text-muted-foreground bg-background", isMobile ? "w-full" : "")}>
|
2025-06-28 09:58:11 -03:00
|
|
|
<File className="w-12 h-12 mb-4" />
|
|
|
|
<div className="text-lg font-medium">No file selected</div>
|
|
|
|
<div className="text-sm">Select a file to view details</div>
|
|
|
|
</div>
|
2025-06-21 21:40:06 -03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2025-06-29 11:58:23 -03:00
|
|
|
<div className={cn("flex flex-col h-full bg-background", isMobile ? "w-full" : "")}>
|
|
|
|
<div className="flex items-center p-2 bg-secondary border-b border-border">
|
2025-06-28 09:58:11 -03:00
|
|
|
<div className="flex items-center gap-2">
|
2025-06-29 11:58:23 -03:00
|
|
|
<TooltipProvider>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" className="hover:bg-accent">
|
|
|
|
<Download className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent className="bg-popover">Download</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
</TooltipProvider>
|
|
|
|
<TooltipProvider>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" className="hover:bg-accent">
|
|
|
|
<Share className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent className="bg-popover">Share</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
</TooltipProvider>
|
|
|
|
<TooltipProvider>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon" className="hover:bg-accent">
|
|
|
|
<Star className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent className="bg-popover">Star</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
</TooltipProvider>
|
2025-06-28 09:58:11 -03:00
|
|
|
</div>
|
|
|
|
<div className="ml-auto">
|
|
|
|
<DropdownMenu>
|
|
|
|
<DropdownMenuTrigger asChild>
|
2025-06-29 11:58:23 -03:00
|
|
|
<Button variant="ghost" size="icon" className="hover:bg-accent">
|
2025-06-28 09:58:11 -03:00
|
|
|
<MoreVertical className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
</DropdownMenuTrigger>
|
2025-06-29 11:58:23 -03:00
|
|
|
<DropdownMenuContent align="end" className="bg-popover">
|
2025-06-28 09:58:11 -03:00
|
|
|
<DropdownMenuItem>Rename</DropdownMenuItem>
|
|
|
|
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
2025-06-29 11:58:23 -03:00
|
|
|
<DropdownMenuItem className="text-destructive">Move to trash</DropdownMenuItem>
|
2025-06-28 09:58:11 -03:00
|
|
|
</DropdownMenuContent>
|
|
|
|
</DropdownMenu>
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
|
|
|
</div>
|
2025-06-29 11:58:23 -03:00
|
|
|
<Separator className="bg-border" />
|
2025-06-28 09:58:11 -03:00
|
|
|
<div className="flex-1 flex-col">
|
|
|
|
<div className="flex items-start gap-4 p-4">
|
2025-06-29 11:58:23 -03:00
|
|
|
<div className="p-2 rounded-lg bg-secondary">
|
2025-06-28 09:58:11 -03:00
|
|
|
{getFileIcon(file)}
|
|
|
|
</div>
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
<div className="font-semibold">{file.name}</div>
|
|
|
|
<div className="text-sm text-muted-foreground">
|
|
|
|
{file.is_dir ? 'Folder' : `${file.type?.toUpperCase() || 'File'} • ${formatFileSize(file.size)}`}
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
|
|
|
</div>
|
2025-06-28 09:58:11 -03:00
|
|
|
{file.modified && (
|
|
|
|
<div className="text-xs text-muted-foreground">
|
|
|
|
{formatDate(file.modified)}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
2025-06-29 11:58:23 -03:00
|
|
|
<Separator className="bg-border" />
|
2025-06-28 09:58:11 -03:00
|
|
|
<div className="p-4 space-y-4">
|
|
|
|
<div>
|
|
|
|
<div className="text-sm font-medium">Location</div>
|
|
|
|
<div className="text-sm text-muted-foreground">/{file.path || ''}</div>
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
2025-06-28 09:58:11 -03:00
|
|
|
<div>
|
|
|
|
<div className="text-sm font-medium">Modified</div>
|
|
|
|
<div className="text-sm text-muted-foreground">
|
|
|
|
{formatDateTime(file.modified)}
|
|
|
|
</div>
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
2025-06-28 09:58:11 -03:00
|
|
|
{!file.is_dir && (
|
|
|
|
<div>
|
|
|
|
<div className="text-sm font-medium">Size</div>
|
|
|
|
<div className="text-sm text-muted-foreground">{formatFileSize(file.size)}</div>
|
|
|
|
</div>
|
|
|
|
)}
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
2025-06-28 09:58:11 -03:00
|
|
|
</div>
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2025-06-28 09:58:11 -03:00
|
|
|
export default function FileManager() {
|
|
|
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
2025-06-21 21:40:06 -03:00
|
|
|
const [currentPath, setCurrentPath] = useState('');
|
|
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
const [filterType, setFilterType] = useState('all');
|
2025-06-28 09:58:11 -03:00
|
|
|
const [selectedFile, setSelectedFile] = useState(null);
|
2025-06-29 11:58:23 -03:00
|
|
|
const [isMobile, setIsMobile] = useState(false);
|
|
|
|
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
|
|
|
const [activePanel, setActivePanel] = useState('files'); // 'tree', 'files', 'preview'
|
2025-06-21 21:40:06 -03:00
|
|
|
|
|
|
|
const currentItem = fileSystemData[currentPath];
|
|
|
|
|
2025-06-28 19:30:35 -03:00
|
|
|
const shortcuts = [
|
|
|
|
[
|
|
|
|
{ key: 'Q', label: 'Rename', action: () => console.log('Rename') },
|
|
|
|
{ key: 'W', label: 'View', action: () => console.log('View') },
|
|
|
|
{ key: 'E', label: 'Edit', action: () => console.log('Edit') },
|
|
|
|
{ key: 'R', label: 'Move', action: () => console.log('Move') },
|
|
|
|
{ key: 'T', label: 'MkDir', action: () => console.log('Make Directory') },
|
|
|
|
{ key: 'Y', label: 'Delete', action: () => console.log('Delete') },
|
|
|
|
{ key: 'U', label: 'Copy', action: () => console.log('Copy') },
|
|
|
|
{ key: 'I', label: 'Cut', action: () => console.log('Cut') },
|
|
|
|
{ key: 'O', label: 'Paste', action: () => console.log('Paste') },
|
|
|
|
{ key: 'P', label: 'Duplicate', action: () => console.log('Duplicate') },
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{ key: 'A', label: 'Select', action: () => console.log('Select') },
|
|
|
|
{ key: 'S', label: 'Select All', action: () => console.log('Select All') },
|
|
|
|
{ key: 'D', label: 'Deselect', action: () => console.log('Deselect') },
|
|
|
|
{ key: 'G', label: 'Details', action: () => console.log('Details') },
|
|
|
|
{ key: 'H', label: 'History', action: () => console.log('History') },
|
|
|
|
{ key: 'J', label: 'Share', action: () => console.log('Share') },
|
|
|
|
{ key: 'K', label: 'Star', action: () => console.log('Star') },
|
|
|
|
{ key: 'L', label: 'Download', action: () => console.log('Download') },
|
|
|
|
{ key: 'Z', label: 'Upload', action: () => console.log('Upload') },
|
|
|
|
{ key: 'X', label: 'Refresh', action: () => console.log('Refresh') },
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
const handleContextAction = (action, file) => {
|
|
|
|
console.log(`Context action: ${action}`, file);
|
|
|
|
switch (action) {
|
|
|
|
case 'star':
|
|
|
|
console.log('Toggle star for', file.name);
|
|
|
|
break;
|
|
|
|
case 'share':
|
|
|
|
console.log('Share', file.name);
|
|
|
|
break;
|
|
|
|
case 'download':
|
|
|
|
console.log('Download', file.name);
|
|
|
|
break;
|
|
|
|
case 'trash':
|
|
|
|
console.log('Move to trash', file.name);
|
|
|
|
break;
|
|
|
|
case 'copy':
|
|
|
|
console.log('Copy', file.name);
|
|
|
|
break;
|
|
|
|
case 'cut':
|
|
|
|
console.log('Cut', file.name);
|
|
|
|
break;
|
|
|
|
case 'rename':
|
|
|
|
console.log('Rename', file.name);
|
|
|
|
break;
|
|
|
|
case 'duplicate':
|
|
|
|
console.log('Duplicate', file.name);
|
|
|
|
break;
|
|
|
|
case 'details':
|
|
|
|
console.log('Show details for', file.name);
|
|
|
|
setSelectedFile(file);
|
2025-06-29 11:58:23 -03:00
|
|
|
if (isMobile) setActivePanel('preview');
|
2025-06-28 19:30:35 -03:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
console.log('Unknown action:', action);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
useEffect(() => {
|
|
|
|
const handleResize = () => {
|
|
|
|
setIsMobile(window.innerWidth < 768);
|
|
|
|
};
|
|
|
|
|
|
|
|
handleResize();
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-06-28 19:30:35 -03:00
|
|
|
const handleKeyDown = (e) => {
|
|
|
|
const isCtrl = e.ctrlKey || e.metaKey;
|
|
|
|
const isShift = e.shiftKey;
|
|
|
|
|
|
|
|
if (isCtrl && e.key === 'n' && isShift) {
|
|
|
|
e.preventDefault();
|
|
|
|
console.log('New folder shortcut');
|
|
|
|
} else if (isCtrl && e.key === 'n') {
|
|
|
|
e.preventDefault();
|
|
|
|
console.log('New file shortcut');
|
|
|
|
} else if (isCtrl && e.key === 'u') {
|
|
|
|
e.preventDefault();
|
|
|
|
console.log('Upload shortcut');
|
|
|
|
} else if (isCtrl && e.key === 'f') {
|
|
|
|
e.preventDefault();
|
2025-06-29 11:58:23 -03:00
|
|
|
document.querySelector('input[placeholder="Search files"]').focus();
|
2025-06-28 19:30:35 -03:00
|
|
|
} else if (e.key === 'Delete' && selectedFile) {
|
|
|
|
e.preventDefault();
|
|
|
|
console.log('Delete shortcut');
|
|
|
|
} else if (e.key === 'F2' && selectedFile) {
|
|
|
|
e.preventDefault();
|
|
|
|
console.log('Rename shortcut');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
|
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
|
|
}, [selectedFile]);
|
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
if (isMobile) {
|
|
|
|
return (
|
|
|
|
<TooltipProvider>
|
|
|
|
<div className="flex flex-col h-screen bg-background">
|
|
|
|
<div className="flex items-center justify-between p-2 bg-secondary border-b border-border">
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
<Sheet open={showMobileMenu} onOpenChange={setShowMobileMenu} >
|
|
|
|
<SheetTrigger asChild>
|
|
|
|
<Button variant="ghost" size="icon">
|
|
|
|
<Menu className="h-5 w-5" />
|
|
|
|
</Button>
|
|
|
|
</SheetTrigger>
|
|
|
|
<SheetContent side="left" className="w-[280px] p-0 bg-background">
|
|
|
|
<FolderTree
|
|
|
|
onSelect={(path) => {
|
|
|
|
setCurrentPath(path);
|
|
|
|
setShowMobileMenu(false);
|
|
|
|
setActivePanel('files');
|
|
|
|
}}
|
|
|
|
selectedPath={currentPath}
|
|
|
|
isMobile={true}
|
|
|
|
/>
|
|
|
|
</SheetContent>
|
|
|
|
</Sheet>
|
|
|
|
<h1 className="text-lg font-bold truncate">{currentItem?.name || 'My Drive'}</h1>
|
|
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
{activePanel !== 'preview' && (
|
|
|
|
<Button variant="ghost" size="icon" onClick={() => setActivePanel('preview')}>
|
|
|
|
<Info className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
{activePanel === 'preview' && (
|
|
|
|
<Button variant="ghost" size="icon" onClick={() => setActivePanel('files')}>
|
|
|
|
<ChevronLeft className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="flex-1 overflow-hidden">
|
|
|
|
{activePanel === 'files' && (
|
|
|
|
<div className="flex flex-col h-full">
|
|
|
|
<div className="p-2 bg-secondary border-b border-border">
|
|
|
|
<div className="relative">
|
|
|
|
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
|
|
<Input
|
|
|
|
placeholder="Search files"
|
|
|
|
className="pl-8 w-full bg-background border border-border"
|
|
|
|
value={searchTerm}
|
|
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<FileList
|
|
|
|
path={currentPath}
|
|
|
|
searchTerm={searchTerm}
|
|
|
|
filterType={filterType}
|
|
|
|
selectedFile={selectedFile}
|
|
|
|
setSelectedFile={(file) => {
|
|
|
|
setSelectedFile(file);
|
|
|
|
setActivePanel('preview');
|
|
|
|
}}
|
|
|
|
onContextAction={handleContextAction}
|
|
|
|
isMobile={true}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{activePanel === 'preview' && (
|
|
|
|
<FileDisplay
|
|
|
|
file={selectedFile}
|
|
|
|
isMobile={true}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Footer shortcuts={shortcuts} isMobile={true} />
|
|
|
|
</div>
|
|
|
|
</TooltipProvider>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-06-21 21:40:06 -03:00
|
|
|
return (
|
2025-06-29 11:58:23 -03:00
|
|
|
<TooltipProvider>
|
|
|
|
<div className="flex flex-col h-[calc(100vh-40px)] bg-background">
|
2025-06-28 19:30:35 -03:00
|
|
|
<ResizablePanelGroup direction="horizontal" className="flex-1 min-h-0">
|
|
|
|
<ResizablePanel
|
|
|
|
defaultSize={20}
|
|
|
|
collapsedSize={4}
|
|
|
|
collapsible={true}
|
|
|
|
minSize={15}
|
|
|
|
maxSize={30}
|
|
|
|
onCollapse={() => setIsCollapsed(true)}
|
|
|
|
onResize={() => setIsCollapsed(false)}
|
|
|
|
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300")}
|
|
|
|
>
|
|
|
|
<FolderTree
|
|
|
|
onSelect={setCurrentPath}
|
|
|
|
selectedPath={currentPath}
|
|
|
|
isCollapsed={isCollapsed}
|
|
|
|
/>
|
|
|
|
</ResizablePanel>
|
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
<ResizableHandle withHandle className="bg-border" />
|
2025-06-28 19:30:35 -03:00
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
<ResizablePanel defaultSize={50} minSize={30} className="bg-background border-r border-border">
|
2025-06-28 19:30:35 -03:00
|
|
|
<Tabs defaultValue="all" className="flex flex-col h-full">
|
2025-06-29 11:58:23 -03:00
|
|
|
<div className="flex items-center px-4 py-2 bg-secondary border-b border-border">
|
2025-06-28 19:30:35 -03:00
|
|
|
<h1 className="text-xl font-bold">{currentItem?.name || 'My Drive'}</h1>
|
2025-06-29 11:58:23 -03:00
|
|
|
<TabsList className="ml-auto bg-background">
|
|
|
|
<TabsTrigger value="all" className="data-[state=active]:bg-accent">All</TabsTrigger>
|
|
|
|
<TabsTrigger value="starred" className="data-[state=active]:bg-accent">Starred</TabsTrigger>
|
2025-06-28 19:30:35 -03:00
|
|
|
</TabsList>
|
|
|
|
</div>
|
2025-06-29 11:58:23 -03:00
|
|
|
<Separator className="bg-border" />
|
|
|
|
<div className="bg-secondary p-4">
|
2025-06-28 19:30:35 -03:00
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
<div className="relative flex-1">
|
|
|
|
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
|
|
<Input
|
|
|
|
placeholder="Search files"
|
2025-06-29 11:58:23 -03:00
|
|
|
className="pl-8 bg-background border border-border"
|
2025-06-28 19:30:35 -03:00
|
|
|
value={searchTerm}
|
|
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<Select value={filterType} onValueChange={setFilterType}>
|
2025-06-29 11:58:23 -03:00
|
|
|
<SelectTrigger className="w-[140px] bg-background border border-border">
|
2025-06-28 19:30:35 -03:00
|
|
|
<SelectValue />
|
|
|
|
</SelectTrigger>
|
2025-06-29 11:58:23 -03:00
|
|
|
<SelectContent className="bg-popover">
|
2025-06-28 19:30:35 -03:00
|
|
|
<SelectItem value="all">All items</SelectItem>
|
|
|
|
<SelectItem value="folders">Folders</SelectItem>
|
|
|
|
<SelectItem value="files">Files</SelectItem>
|
|
|
|
<SelectItem value="starred">Starred</SelectItem>
|
|
|
|
</SelectContent>
|
|
|
|
</Select>
|
2025-06-28 09:58:11 -03:00
|
|
|
</div>
|
2025-06-21 21:40:06 -03:00
|
|
|
</div>
|
2025-06-28 19:30:35 -03:00
|
|
|
<TabsContent value="all" className="m-0 flex-1">
|
|
|
|
<FileList
|
|
|
|
path={currentPath}
|
|
|
|
searchTerm={searchTerm}
|
|
|
|
filterType={filterType}
|
|
|
|
selectedFile={selectedFile}
|
|
|
|
setSelectedFile={setSelectedFile}
|
|
|
|
onContextAction={handleContextAction}
|
|
|
|
/>
|
|
|
|
</TabsContent>
|
|
|
|
<TabsContent value="starred" className="m-0 flex-1">
|
|
|
|
<FileList
|
|
|
|
path={currentPath}
|
|
|
|
searchTerm={searchTerm}
|
|
|
|
filterType="starred"
|
|
|
|
selectedFile={selectedFile}
|
|
|
|
setSelectedFile={setSelectedFile}
|
|
|
|
onContextAction={handleContextAction}
|
|
|
|
/>
|
|
|
|
</TabsContent>
|
|
|
|
</Tabs>
|
|
|
|
</ResizablePanel>
|
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
<ResizableHandle withHandle className="bg-border" />
|
2025-06-28 19:30:35 -03:00
|
|
|
|
2025-06-29 11:58:23 -03:00
|
|
|
<ResizablePanel defaultSize={30} minSize={25} className="bg-background">
|
2025-06-28 19:30:35 -03:00
|
|
|
<FileDisplay file={selectedFile} />
|
|
|
|
</ResizablePanel>
|
|
|
|
</ResizablePanelGroup>
|
2025-06-29 11:58:23 -03:00
|
|
|
<Footer shortcuts={shortcuts} />
|
|
|
|
</div>
|
|
|
|
</TooltipProvider>
|
2025-06-21 21:40:06 -03:00
|
|
|
);
|
2025-06-29 11:58:23 -03:00
|
|
|
}
|