New context menu, info pane fixes, pinned item syncing, and navigation bug fixes
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m19s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m19s
This commit is contained in:
parent
a52f1d63f7
commit
5200654d0f
5 changed files with 111 additions and 27 deletions
|
|
@ -76,6 +76,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
const [searchResults, setSearchResults] = useState<FileItem[] | null>(null);
|
||||
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
|
||||
const [contextMenu, setContextMenu] = useState<{ x: number; y: number; file: FileItem } | null>(null);
|
||||
const [bgContextMenu, setBgContextMenu] = useState<{ x: number; y: number } | null>(null);
|
||||
const [renaming, setRenaming] = useState<string | null>(null);
|
||||
const [newName, setNewName] = useState('');
|
||||
const [showNewFolder, setShowNewFolder] = useState(false);
|
||||
|
|
@ -109,6 +110,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
if (s) {
|
||||
if (s.grid_size) setGridSize(s.grid_size);
|
||||
if (s.trash_auto_purge_days) setTrashAutoPurgeDays(parseInt(s.trash_auto_purge_days, 10));
|
||||
if (s.pinned_sidebar_expanded === 'true') setPinnedExpanded(true);
|
||||
if (s.pinned_order) {
|
||||
try {
|
||||
setPinnedOrder(JSON.parse(s.pinned_order));
|
||||
|
|
@ -214,11 +216,12 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
|
||||
// Close context menu on click outside
|
||||
useEffect(() => {
|
||||
function handleClick() {
|
||||
const handleClickOutside = () => {
|
||||
setContextMenu(null);
|
||||
}
|
||||
window.addEventListener('click', handleClick);
|
||||
return () => window.removeEventListener('click', handleClick);
|
||||
setBgContextMenu(null);
|
||||
};
|
||||
window.addEventListener('click', handleClickOutside);
|
||||
return () => window.removeEventListener('click', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
async function loadPinned() {
|
||||
|
|
@ -339,6 +342,14 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleBgContextMenu(e: React.MouseEvent) {
|
||||
// Only show if the click was on the container itself, not on a file item
|
||||
if ((e.target as HTMLElement).closest('[data-file-item]')) return;
|
||||
e.preventDefault();
|
||||
setContextMenu(null);
|
||||
setBgContextMenu({ x: e.clientX, y: e.clientY });
|
||||
}
|
||||
|
||||
function handleContextMenu(e: React.MouseEvent, file: FileItem) {
|
||||
e.preventDefault();
|
||||
setContextMenu({ x: e.clientX, y: e.clientY, file });
|
||||
|
|
@ -724,7 +735,11 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
{/* Pinned Collapsible Section */}
|
||||
<div className="py-1">
|
||||
<button
|
||||
onClick={() => setPinnedExpanded(!pinnedExpanded)}
|
||||
onClick={() => {
|
||||
const next = !pinnedExpanded;
|
||||
setPinnedExpanded(next);
|
||||
api.updateSettings({ pinned_sidebar_expanded: next ? 'true' : 'false' }).catch(console.error);
|
||||
}}
|
||||
className="w-full flex items-center justify-between px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-150 hover:bg-white/5"
|
||||
style={{ color: 'var(--color-text-secondary)' }}
|
||||
>
|
||||
|
|
@ -748,6 +763,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
pinnedFiles.map((file) => (
|
||||
<div
|
||||
key={file.path}
|
||||
data-file-item
|
||||
draggable
|
||||
onDragStart={(e) => {
|
||||
e.dataTransfer.setData('text/plain', file.path);
|
||||
|
|
@ -764,7 +780,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
}}
|
||||
className="group flex items-center justify-between px-10 py-2 text-sm font-medium cursor-pointer rounded-lg transition-colors hover:bg-white/5 gap-3"
|
||||
style={{ color: 'var(--color-text-secondary)' }}
|
||||
onClick={() => navigateTo(file.path)}
|
||||
onClick={() => { setActiveSection('files'); navigateTo(file.path); }}
|
||||
>
|
||||
<div className="w-4 h-4 flex items-center justify-center flex-shrink-0 [&>svg]:w-4 [&>svg]:h-4">
|
||||
{getFileIcon(file)}
|
||||
|
|
@ -985,7 +1001,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
) : (
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* File Area */}
|
||||
<div className="flex-1 overflow-y-auto p-6 relative">
|
||||
<div className="flex-1 overflow-y-auto p-6 relative" onContextMenu={activeSection === 'files' ? handleBgContextMenu : undefined}>
|
||||
{/* Drag and drop overlay */}
|
||||
<AnimatePresence>
|
||||
{dragOver && (
|
||||
|
|
@ -1077,19 +1093,20 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
)}
|
||||
<div className="grid gap-3" style={{ gridTemplateColumns: `repeat(auto-fill, minmax(${gridSize}px, 1fr))` }}>
|
||||
{group.files.map((file, idx) => (
|
||||
<motion.div
|
||||
key={file.path}
|
||||
draggable
|
||||
onDragStart={(e: any) => handleItemDragStart(e, file.path)}
|
||||
onDragOver={(e: any) => handleItemDragOver(e, file)}
|
||||
onDragLeave={(e: any) => handleItemDragLeave(e, file)}
|
||||
onDrop={(e: any) => handleItemDrop(e, file)}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: idx * 0.02 }}
|
||||
onClick={(e) => handleFileClick(file, e)}
|
||||
onContextMenu={(e) => handleContextMenu(e, file)}
|
||||
className={`group relative p-4 rounded-xl cursor-pointer transition-all duration-150 ${draggedFile === file.path ? 'opacity-50' : ''}`}
|
||||
<motion.div
|
||||
key={file.path}
|
||||
data-file-item
|
||||
draggable
|
||||
onDragStart={(e: any) => handleItemDragStart(e, file.path)}
|
||||
onDragOver={(e: any) => handleItemDragOver(e, file)}
|
||||
onDragLeave={(e: any) => handleItemDragLeave(e, file)}
|
||||
onDrop={(e: any) => handleItemDrop(e, file)}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: idx * 0.02 }}
|
||||
onClick={(e) => handleFileClick(file, e)}
|
||||
onContextMenu={(e) => handleContextMenu(e, file)}
|
||||
className={`group relative p-4 rounded-xl cursor-pointer transition-all duration-150 ${draggedFile === file.path ? 'opacity-50' : ''}`}
|
||||
style={{
|
||||
backgroundColor: selectedFiles.has(file.path) || dragOverFolder === file.path ? 'var(--color-accent-subtle)' : 'var(--color-bg-elevated)',
|
||||
border: `1px solid ${selectedFiles.has(file.path) || dragOverFolder === file.path ? 'var(--color-accent)' : 'var(--color-border-subtle)'}`,
|
||||
|
|
@ -1170,6 +1187,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
{group.files.map((file, idx) => (
|
||||
<motion.div
|
||||
key={file.path}
|
||||
data-file-item
|
||||
draggable
|
||||
onDragStart={(e: any) => handleItemDragStart(e, file.path)}
|
||||
onDragOver={(e: any) => handleItemDragOver(e, file)}
|
||||
|
|
@ -1399,6 +1417,36 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* === BACKGROUND CONTEXT MENU === */}
|
||||
<AnimatePresence>
|
||||
{bgContextMenu && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
transition={{ duration: 0.1 }}
|
||||
className="fixed z-50 w-48 rounded-xl py-1.5 overflow-hidden"
|
||||
style={{
|
||||
left: bgContextMenu.x,
|
||||
top: bgContextMenu.y,
|
||||
backgroundColor: 'var(--color-bg-elevated)',
|
||||
border: '1px solid var(--color-border)',
|
||||
boxShadow: 'var(--shadow-lg)',
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ContextMenuItem
|
||||
icon={<FolderPlus className="w-4 h-4" />}
|
||||
label="New Folder"
|
||||
onClick={() => {
|
||||
setShowNewFolder(true);
|
||||
setBgContextMenu(null);
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Upload Progress Panel */}
|
||||
<AnimatePresence>
|
||||
{uploads.length > 0 && (
|
||||
|
|
|
|||
Reference in a new issue