Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/components/file-tree/hooks/useFileTreeUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,17 @@ export const useFileTreeUpload = ({
],
);

/**
* Upload the picked files into the given target folder.
*
* @param fileList Files chosen via the File Explorer input or drop.
* @param targetPath Project-relative folder to upload into; defaults to ''
* (the project root). Validated server-side via
* validatePathInProject.
*/
const handleFileSelect = useCallback(
async (fileList: FileList | File[]) => {
await uploadFiles(Array.from(fileList), '');
async (fileList: FileList | File[], targetPath = '') => {
await uploadFiles(Array.from(fileList), targetPath);
},
[uploadFiles],
);
Expand Down
31 changes: 29 additions & 2 deletions src/components/file-tree/view/FileTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,20 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
return <Icon className={cn(ICON_SIZE_CLASS, color)} />;
}, []);

// Centralized click behavior keeps file actions identical across all presentation modes.
const [selectedUploadDir, setSelectedUploadDir] = useState<string>('');

/**
* Centralized click behavior keeps file actions identical across all
* presentation modes. Clicking a directory also marks it as the upload
* target, so subsequent File Explorer uploads land in that folder instead
* of the project root.
*
* @param item The file-tree node that was clicked.
*/
const handleItemClick = useCallback(
(item: FileTreeNode) => {
if (item.type === 'directory') {
setSelectedUploadDir(item.path);
toggleDirectory(item.path);
return;
}
Expand Down Expand Up @@ -150,7 +160,7 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
onViewModeChange={changeViewMode}
searchQuery={searchQuery}
onSearchQueryChange={setSearchQuery}
onUploadFiles={upload.handleFileSelect}
onUploadFiles={(files) => upload.handleFileSelect(files, selectedUploadDir)}
onNewFile={() => operations.handleStartCreate('', 'file')}
onNewFolder={() => operations.handleStartCreate('', 'directory')}
onRefresh={refreshFiles}
Expand All @@ -161,6 +171,23 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
uploadProgress={upload.uploadProgress?.progress ?? null}
/>

{selectedUploadDir && (
<div className="flex items-center gap-1.5 px-3 py-0.5 text-xs text-muted-foreground">
<span className="truncate">
{t('fileTree.uploadTarget', 'Upload target')}: {selectedUploadDir}
</span>
<button
type="button"
onClick={() => setSelectedUploadDir('')}
className="shrink-0 rounded p-0.5 hover:text-foreground"
title={t('fileTree.uploadTargetReset', 'Reset upload target to project root')}
aria-label={t('fileTree.uploadTargetReset', 'Reset upload target to project root')}
>
<X className="h-3 w-3" />
</button>
</div>
)}

<FileTreeUploadProgress upload={upload.uploadProgress} />

{viewMode === 'detailed' && filteredFiles.length > 0 && <FileTreeDetailedColumns />}
Expand Down